tried to add assimp (spoilers, it did not go well, so hey! now we have separate fiels now.)
Some checks are pending
Build C++ Project with Fedora Container and Create Release / build (push) Waiting to run

This commit is contained in:
Anemunt
2025-12-05 00:34:39 -05:00
parent 157e9ed6a5
commit 66df544651
3118 changed files with 1594508 additions and 3346 deletions

3
.gitmodules vendored
View File

@@ -8,3 +8,6 @@
[submodule "src/ThirdParty/ImGuizmo"]
path = src/ThirdParty/ImGuizmo
url = https://github.com/CedricGuillemet/ImGuizmo.git
[submodule "src/ThirdParty/assimp"]
path = src/ThirdParty/assimp
url = https://github.com/assimp/assimp.git

View File

@@ -33,12 +33,12 @@ target_include_directories(glad PUBLIC src/ThirdParty/glad)
add_library(glm INTERFACE)
target_include_directories(glm INTERFACE src/ThirdParty/glm)
# ImGuizmo ←←← THIS WAS THE BUG
# ImGuizmo
add_library(imguizmo STATIC
src/ThirdParty/ImGuizmo/ImGuizmo.cpp
)
target_include_directories(imguizmo PUBLIC src/ThirdParty/ImGuizmo)
target_link_libraries(imguizmo PUBLIC imgui glm) # ImGuizmo uses ImGui + GLM
target_link_libraries(imguizmo PUBLIC imgui glm)
# Dear ImGui
set(IMGUI_DIR ${PROJECT_SOURCE_DIR}/src/ThirdParty/imgui)
@@ -54,49 +54,73 @@ add_library(imgui STATIC
target_include_directories(imgui PUBLIC ${IMGUI_DIR} ${IMGUI_DIR}/backends)
target_link_libraries(imgui PRIVATE glfw)
# ==================== Your code ====================
file(GLOB_RECURSE PROJECT_SOURCES
src/*.cpp
src/*.c
# ==================== Your code (separated files) ====================
set(ENGINE_SOURCES
src/Camera.cpp
src/Rendering.cpp
src/ProjectManager.cpp
src/EditorUI.cpp
src/Engine.cpp
src/EnginePanels.cpp
src/Shaders/Shader_Manager/Shader.cpp
src/Skybox/Skybox.cpp
src/Textures/Texture.cpp
src/WinView/Window.cpp
)
list(FILTER PROJECT_SOURCES EXCLUDE REGEX "src/ThirdParty/|src/main\\.cpp")
add_library(core STATIC ${PROJECT_SOURCES})
target_include_directories(core PUBLIC include)
set(ENGINE_HEADERS
src/Common.h
src/SceneObject.h
src/Camera.h
src/Rendering.h
src/ProjectManager.h
src/EditorUI.h
src/Engine.h
)
add_library(core STATIC ${ENGINE_SOURCES} ${ENGINE_HEADERS})
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "Disable Assimp warnings as errors" FORCE)
add_subdirectory(src/ThirdParty/assimp EXCLUDE_FROM_ALL)
target_link_libraries(core PUBLIC assimp)
target_include_directories(core PUBLIC
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src/ThirdParty/assimp/include
)
target_link_libraries(core PUBLIC glad glm imgui imguizmo)
# ==================== Executable ====================
add_executable(main src/main.cpp)
add_executable(Modularity src/main.cpp)
# Link order matters on Linux
if(NOT WIN32)
find_package(X11 REQUIRED)
target_include_directories(main PRIVATE ${X11_INCLUDE_DIR})
target_include_directories(Modularity PRIVATE ${X11_INCLUDE_DIR})
target_link_libraries(main PRIVATE
core # your code + static libs
imgui # ImGui
imguizmo # ImGuizmo
glad # GLAD
glm # header-only, harmless
glfw # GLFW depends on X11
target_link_libraries(Modularity PRIVATE
core
imgui
imguizmo
glad
glm
glfw
OpenGL::GL
pthread
dl
${X11_LIBRARIES} # Must come AFTER libraries that need X11
${X11_LIBRARIES}
Xrandr
Xi
Xinerama
Xcursor
)
else()
target_link_libraries(main PRIVATE core glfw OpenGL::GL)
target_link_libraries(Modularity PRIVATE core glfw OpenGL::GL)
endif()
# ==================== Copy Resources folder after build ====================
add_custom_command(TARGET main POST_BUILD
add_custom_command(TARGET Modularity POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/Resources
$<TARGET_FILE_DIR:main>/Resources
)
$<TARGET_FILE_DIR:Modularity>/Resources
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

122
src/Camera.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "Camera.h"
void Camera::processMouse(double xpos, double ypos) {
if (ImGuizmo::IsUsing() || ImGuizmo::IsOver()) {
return;
}
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = (xpos - lastX) * SENSITIVITY;
float yoffset = (lastY - ypos) * SENSITIVITY;
lastX = xpos;
lastY = ypos;
yaw += xoffset;
pitch += yoffset;
if (pitch > 89.0f) pitch = 89.0f;
if (pitch < -89.0f) pitch = -89.0f;
glm::vec3 direction;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
front = glm::normalize(direction);
}
void Camera::processKeyboard(float deltaTime, GLFWwindow* window) {
const float CAMERA_SPEED = 5.0f;
const float SPRINT_SPEED = 10.0f;
const float ACCELERATION = 15.0f;
float currentSpeed = CAMERA_SPEED;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
currentSpeed = SPRINT_SPEED;
}
glm::vec3 desiredDir(0.0f);
bool isMoving = false;
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
desiredDir += front;
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
desiredDir -= front;
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
desiredDir -= glm::normalize(glm::cross(front, up));
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
desiredDir += glm::normalize(glm::cross(front, up));
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) {
desiredDir -= up;
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) {
desiredDir += up;
isMoving = true;
}
if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
glm::vec3 targetVelocity(0.0f);
if (isMoving) {
float length = glm::length(desiredDir);
if (length > 0.0001f) {
desiredDir = desiredDir / length;
targetVelocity = desiredDir * currentSpeed;
} else {
targetVelocity = glm::vec3(0.0f);
}
}
float smoothFactor = 1.0f - std::exp(-ACCELERATION * deltaTime);
velocity = glm::mix(velocity, targetVelocity, smoothFactor);
position += velocity * deltaTime;
}
glm::mat4 Camera::getViewMatrix() const {
return glm::lookAt(position, position + front, up);
}
// ViewportController implementation
void ViewportController::updateFocusFromImGui(bool windowFocused) {
if (!windowFocused && viewportFocused && !manualUnfocus) {
viewportFocused = false;
}
}
void ViewportController::setFocused(bool focused) {
viewportFocused = focused;
}
bool ViewportController::isViewportFocused() const {
return viewportFocused;
}
void ViewportController::clearManualUnfocus() {
manualUnfocus = false;
}
void ViewportController::update(GLFWwindow* window, bool& cursorLocked) {
if (viewportFocused && glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
viewportFocused = false;
manualUnfocus = true;
cursorLocked = false;
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
}

34
src/Camera.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "Common.h"
#include <GLFW/glfw3.h>
class Camera {
public:
glm::vec3 position = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 velocity = glm::vec3(0.0f);
float yaw = -90.0f;
float pitch = 0.0f;
float speed = CAMERA_SPEED;
float lastX = 400.0f, lastY = 300.0f;
bool firstMouse = true;
void processMouse(double xpos, double ypos);
void processKeyboard(float deltaTime, GLFWwindow* window);
glm::mat4 getViewMatrix() const;
};
class ViewportController {
private:
bool viewportFocused = false;
bool manualUnfocus = false;
public:
void updateFocusFromImGui(bool windowFocused);
void setFocused(bool focused);
bool isViewportFocused() const;
void clearManualUnfocus();
void update(GLFWwindow* window, bool& cursorLocked);
};

54
src/Common.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <ctime>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <string>
#include <vector>
#include <memory>
#include <glad/glad.h>
#include "ThirdParty/imgui/imgui.h"
#include "ThirdParty/imgui/imgui_internal.h"
#include "ThirdParty/imgui/backends/imgui_impl_glfw.h"
#include "ThirdParty/imgui/backends/imgui_impl_opengl3.h"
#include "ThirdParty/ImGuizmo/ImGuizmo.h"
#include "ThirdParty/glm/glm.hpp"
#include "ThirdParty/glm/gtc/matrix_transform.hpp"
#include "ThirdParty/glm/gtc/type_ptr.hpp"
#include "ThirdParty/glm/gtc/quaternion.hpp"
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#endif
namespace fs = std::filesystem;
// Constants
constexpr float SENSITIVITY = 0.1f;
constexpr float CAMERA_SPEED = 2.5f;
constexpr float FOV = 45.0f;
constexpr float NEAR_PLANE = 0.1f;
constexpr float FAR_PLANE = 100.0f;
constexpr float PI = 3.14159265359f;
// Forward declarations
class Mesh;
class OBJLoader;
class Renderer;
class Camera;
class ViewportController;
class Project;
class ProjectManager;
class Engine;
// Global OBJ loader instance (extern declaration)
extern OBJLoader g_objLoader;

278
src/EditorUI.cpp Normal file
View File

@@ -0,0 +1,278 @@
#include "EditorUI.h"
// FileBrowser implementation
FileBrowser::FileBrowser() {
currentPath = fs::current_path();
projectRoot = currentPath;
}
void FileBrowser::refresh() {
entries.clear();
try {
for (const auto& entry : fs::directory_iterator(currentPath)) {
// Skip hidden files if not showing them
std::string filename = entry.path().filename().string();
if (!showHiddenFiles && !filename.empty() && filename[0] == '.') {
continue;
}
// Apply search filter if any
if (!matchesFilter(entry)) {
continue;
}
entries.push_back(entry);
}
// Sort: folders first, then alphabetically
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
if (a.is_directory() != b.is_directory()) {
return a.is_directory() > b.is_directory();
}
return a.path().filename().string() < b.path().filename().string();
});
} catch (...) {
}
needsRefresh = false;
}
void FileBrowser::navigateUp() {
if (currentPath.has_parent_path() && currentPath != currentPath.root_path()) {
// Don't go above project root
if (currentPath != projectRoot) {
navigateTo(currentPath.parent_path());
}
}
}
void FileBrowser::navigateTo(const fs::path& path) {
if (fs::is_directory(path)) {
// Add to history
if (historyIndex < 0 || pathHistory.empty() || pathHistory[historyIndex] != currentPath) {
// Clear forward history
if (historyIndex >= 0 && historyIndex < (int)pathHistory.size() - 1) {
pathHistory.erase(pathHistory.begin() + historyIndex + 1, pathHistory.end());
}
pathHistory.push_back(currentPath);
historyIndex = (int)pathHistory.size() - 1;
}
currentPath = path;
needsRefresh = true;
}
}
void FileBrowser::navigateBack() {
if (historyIndex > 0) {
historyIndex--;
currentPath = pathHistory[historyIndex];
needsRefresh = true;
}
}
void FileBrowser::navigateForward() {
if (historyIndex < (int)pathHistory.size() - 1) {
historyIndex++;
currentPath = pathHistory[historyIndex];
needsRefresh = true;
}
}
void FileBrowser::setProjectRoot(const fs::path& root) {
projectRoot = root;
currentPath = root;
pathHistory.clear();
historyIndex = -1;
needsRefresh = true;
}
FileCategory FileBrowser::getFileCategory(const fs::directory_entry& entry) const {
if (entry.is_directory()) return FileCategory::Folder;
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
// Scene files
if (ext == ".modu" || ext == ".scene") return FileCategory::Scene;
// Model files
if (ext == ".fbx" || ext == ".obj" || ext == ".gltf" || ext == ".glb" ||
ext == ".dae" || ext == ".blend" || ext == ".3ds" || ext == ".ply" ||
ext == ".stl" || ext == ".x" || ext == ".md5mesh") {
return FileCategory::Model;
}
// Texture files
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" ||
ext == ".tga" || ext == ".dds" || ext == ".hdr") {
return FileCategory::Texture;
}
// Shader files
if (ext == ".glsl" || ext == ".vert" || ext == ".frag" || ext == ".hlsl" ||
ext == ".shader") {
return FileCategory::Shader;
}
// Script files
if (ext == ".cpp" || ext == ".c" || ext == ".h" || ext == ".hpp" ||
ext == ".lua" || ext == ".py" || ext == ".cs") {
return FileCategory::Script;
}
// Audio files
if (ext == ".wav" || ext == ".mp3" || ext == ".ogg" || ext == ".flac") {
return FileCategory::Audio;
}
// Text files
if (ext == ".txt" || ext == ".md" || ext == ".json" || ext == ".xml" ||
ext == ".yaml" || ext == ".ini" || ext == ".cfg") {
return FileCategory::Text;
}
return FileCategory::Unknown;
}
const char* FileBrowser::getFileIcon(const fs::directory_entry& entry) const {
FileCategory category = getFileCategory(entry);
switch (category) {
case FileCategory::Folder: return "folder";
case FileCategory::Scene: return "scene";
case FileCategory::Model: return "model";
case FileCategory::Texture: return "image";
case FileCategory::Shader: return "shader";
case FileCategory::Script: return "code";
case FileCategory::Audio: return "audio";
case FileCategory::Text: return "text";
default: return "file";
}
}
bool FileBrowser::isModelFile(const fs::directory_entry& entry) const {
return getFileCategory(entry) == FileCategory::Model;
}
bool FileBrowser::isSceneFile(const fs::directory_entry& entry) const {
return getFileCategory(entry) == FileCategory::Scene;
}
bool FileBrowser::isTextureFile(const fs::directory_entry& entry) const {
return getFileCategory(entry) == FileCategory::Texture;
}
bool FileBrowser::isOBJFile(const fs::directory_entry& entry) const {
if (entry.is_directory()) return false;
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext == ".obj";
}
bool FileBrowser::matchesFilter(const fs::directory_entry& entry) const {
if (searchFilter.empty()) return true;
std::string filename = entry.path().filename().string();
std::string filterLower = searchFilter;
std::string filenameLower = filename;
std::transform(filterLower.begin(), filterLower.end(), filterLower.begin(), ::tolower);
std::transform(filenameLower.begin(), filenameLower.end(), filenameLower.begin(), ::tolower);
return filenameLower.find(filterLower) != std::string::npos;
}
void applyModernTheme() {
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.12f, 0.12f, 0.14f, 0.98f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.28f, 0.32f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.06f, 0.06f, 0.08f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.40f, 0.48f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.36f, 0.42f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.55f, 0.78f, 1.00f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.45f, 0.72f, 0.95f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.45f, 0.72f, 0.95f, 0.95f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.45f, 0.72f, 0.95f, 0.70f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.45f, 0.72f, 0.95f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
style.WindowRounding = 4.0f;
style.ChildRounding = 4.0f;
style.FrameRounding = 4.0f;
style.PopupRounding = 4.0f;
style.ScrollbarRounding = 4.0f;
style.GrabRounding = 4.0f;
style.TabRounding = 4.0f;
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 4.0f);
style.ItemInnerSpacing = ImVec2(4.0f, 4.0f);
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 0.0f;
style.PopupBorderSize = 1.0f;
}
void setupDockspace() {
static bool dockspaceOpen = true;
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None;
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpace", &dockspaceOpen, windowFlags);
ImGui::PopStyleVar(3);
ImGuiID dockspaceId = ImGui::GetID("MainDockspace");
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
ImGui::End();
}

63
src/EditorUI.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include "Common.h"
enum class FileBrowserViewMode {
List,
Grid
};
enum class FileCategory {
Folder,
Scene,
Model,
Texture,
Shader,
Script,
Audio,
Text,
Unknown
};
class FileBrowser {
public:
fs::path currentPath;
fs::path selectedFile;
fs::path projectRoot; // Root of current project
std::vector<fs::directory_entry> entries;
bool needsRefresh = true;
FileBrowserViewMode viewMode = FileBrowserViewMode::Grid;
float iconSize = 64.0f;
float padding = 8.0f;
std::string searchFilter;
bool showHiddenFiles = false;
std::vector<fs::path> pathHistory;
int historyIndex = -1;
FileBrowser();
void refresh();
void navigateUp();
void navigateTo(const fs::path& path);
void navigateBack();
void navigateForward();
void setProjectRoot(const fs::path& root);
const char* getFileIcon(const fs::directory_entry& entry) const;
FileCategory getFileCategory(const fs::directory_entry& entry) const;
bool isModelFile(const fs::directory_entry& entry) const;
bool isSceneFile(const fs::directory_entry& entry) const;
bool isTextureFile(const fs::directory_entry& entry) const;
bool matchesFilter(const fs::directory_entry& entry) const;
// Legacy compatibility
bool isOBJFile(const fs::directory_entry& entry) const;
};
// Apply the modern dark theme to ImGui
void applyModernTheme();
// Setup ImGui dockspace for the editor
void setupDockspace();

550
src/Engine.cpp Normal file
View File

@@ -0,0 +1,550 @@
#include "Engine.h"
#include <iostream>
void window_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
SceneObject* Engine::getSelectedObject() {
if (selectedObjectId == -1) return nullptr;
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
return (it != sceneObjects.end()) ? &(*it) : nullptr;
}
void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale) {
pos = glm::vec3(matrix[3]);
scale.x = glm::length(glm::vec3(matrix[0]));
scale.y = glm::length(glm::vec3(matrix[1]));
scale.z = glm::length(glm::vec3(matrix[2]));
glm::mat3 rotMat(matrix);
if (scale.x != 0.0f) rotMat[0] /= scale.x;
if (scale.y != 0.0f) rotMat[1] /= scale.y;
if (scale.z != 0.0f) rotMat[2] /= scale.z;
rot = glm::eulerAngles(glm::quat_cast(rotMat));
}
bool Engine::init() {
std::cerr << "[DEBUG] Creating window..." << std::endl;
editorWindow = window.makeWindow();
if (!editorWindow) {
std::cerr << "[DEBUG] Window creation failed!" << std::endl;
return false;
}
std::cerr << "[DEBUG] Window created successfully" << std::endl;
glfwSetWindowUserPointer(editorWindow, this);
glfwSetWindowSizeCallback(editorWindow, window_size_callback);
auto mouse_cb = [](GLFWwindow* window, double xpos, double ypos) {
auto* engine = static_cast<Engine*>(glfwGetWindowUserPointer(window));
if (!engine) return;
int cursorMode = glfwGetInputMode(window, GLFW_CURSOR);
if (!engine->viewportController.isViewportFocused() || cursorMode != GLFW_CURSOR_DISABLED) {
return;
}
engine->camera.processMouse(xpos, ypos);
};
glfwSetCursorPosCallback(editorWindow, mouse_cb);
std::cerr << "[DEBUG] Setting up ImGui..." << std::endl;
setupImGui();
std::cerr << "[DEBUG] ImGui setup complete" << std::endl;
logToConsole("Engine initialized - Waiting for project selection");
return true;
}
bool Engine::initRenderer() {
if (rendererInitialized) return true;
try {
renderer.initialize();
rendererInitialized = true;
return true;
} catch (...) {
return false;
}
}
void Engine::run() {
std::cerr << "[DEBUG] Entering main loop, showLauncher=" << showLauncher << std::endl;
while (!glfwWindowShouldClose(editorWindow)) {
if (glfwGetWindowAttrib(editorWindow, GLFW_ICONIFIED)) {
ImGui_ImplGlfw_Sleep(10);
continue;
}
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
glfwPollEvents();
if (!showLauncher) {
handleKeyboardShortcuts();
}
viewportController.update(editorWindow, cursorLocked);
if (!viewportController.isViewportFocused() && cursorLocked) {
cursorLocked = false;
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
camera.firstMouse = true;
}
if (viewportController.isViewportFocused() && cursorLocked) {
camera.processKeyboard(deltaTime, editorWindow);
}
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
glm::mat4 view = camera.getViewMatrix();
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
if (aspect <= 0.0f) aspect = 1.0f;
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
renderer.beginRender(view, proj);
for (const auto& obj : sceneObjects) {
renderer.renderObject(obj);
}
renderer.renderSkybox(view, proj);
renderer.endRender();
}
if (firstFrame) {
std::cerr << "[DEBUG] First frame: starting ImGui NewFrame" << std::endl;
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if (firstFrame) {
std::cerr << "[DEBUG] First frame: ImGui NewFrame complete, rendering UI..." << std::endl;
}
if (showLauncher) {
if (firstFrame) {
std::cerr << "[DEBUG] First frame: calling renderLauncher()" << std::endl;
}
renderLauncher();
} else {
setupDockspace();
renderMainMenuBar();
if (!viewportFullscreen) {
if (showHierarchy) renderHierarchyPanel();
if (showInspector) renderInspectorPanel();
if (showFileBrowser) renderFileBrowserPanel();
if (showConsole) renderConsolePanel();
if (showProjectBrowser) renderProjectBrowserPanel();
}
renderViewport();
renderDialogs();
}
if (firstFrame) {
std::cerr << "[DEBUG] First frame: UI rendering complete, finalizing frame..." << std::endl;
}
int displayW, displayH;
glfwGetFramebufferSize(editorWindow, &displayW, &displayH);
glViewport(0, 0, displayW, displayH);
glClearColor(0.1f, 0.1f, 0.12f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
glfwSwapBuffers(editorWindow);
if (firstFrame) {
std::cerr << "[DEBUG] First frame complete!" << std::endl;
}
firstFrame = false;
}
std::cerr << "[DEBUG] Exiting main loop" << std::endl;
}
void Engine::shutdown() {
if (projectManager.currentProject.isLoaded && projectManager.currentProject.hasUnsavedChanges) {
saveCurrentScene();
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwTerminate();
}
void Engine::importOBJToScene(const std::string& filepath, const std::string& objectName) {
std::string errorMsg;
int meshId = g_objLoader.loadOBJ(filepath, errorMsg);
if (meshId < 0) {
addConsoleMessage("Failed to load OBJ: " + errorMsg, ConsoleMessageType::Error);
return;
}
int id = nextObjectId++;
std::string name = objectName.empty() ? fs::path(filepath).stem().string() : objectName;
SceneObject obj(name, ObjectType::OBJMesh, id);
obj.meshPath = filepath;
obj.meshId = meshId;
sceneObjects.push_back(obj);
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
const auto* meshInfo = g_objLoader.getMeshInfo(meshId);
if (meshInfo) {
addConsoleMessage("Imported OBJ: " + name + " (" +
std::to_string(meshInfo->vertexCount) + " vertices, " +
std::to_string(meshInfo->faceCount) + " faces)",
ConsoleMessageType::Success);
} else {
addConsoleMessage("Imported OBJ: " + name, ConsoleMessageType::Success);
}
}
void Engine::handleKeyboardShortcuts() {
static bool f11Pressed = false;
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_PRESS && !f11Pressed) {
viewportFullscreen = !viewportFullscreen;
f11Pressed = true;
}
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_RELEASE) {
f11Pressed = false;
}
static bool ctrlSPressed = false;
bool ctrlDown = glfwGetKey(editorWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
glfwGetKey(editorWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_S) == GLFW_PRESS && !ctrlSPressed) {
if (projectManager.currentProject.isLoaded) {
saveCurrentScene();
}
ctrlSPressed = true;
}
if (glfwGetKey(editorWindow, GLFW_KEY_S) == GLFW_RELEASE) {
ctrlSPressed = false;
}
static bool ctrlNPressed = false;
if (ctrlDown && glfwGetKey(editorWindow, GLFW_KEY_N) == GLFW_PRESS && !ctrlNPressed) {
if (projectManager.currentProject.isLoaded) {
showNewSceneDialog = true;
memset(newSceneName, 0, sizeof(newSceneName));
}
ctrlNPressed = true;
}
if (glfwGetKey(editorWindow, GLFW_KEY_N) == GLFW_RELEASE) {
ctrlNPressed = false;
}
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE;
if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
}
if (ImGui::IsKeyPressed(ImGuiKey_LeftCtrl)) useSnap = !useSnap;
}
void Engine::OpenProjectPath(const std::string& path) {
if (projectManager.loadProject(path)) {
if (!initRenderer()) {
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
} else {
showLauncher = false;
loadRecentScenes();
addConsoleMessage("Opened project: " + projectManager.currentProject.name, ConsoleMessageType::Info);
}
} else {
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
}
}
void Engine::createNewProject(const char* name, const char* location) {
fs::path basePath(location);
fs::create_directories(basePath);
Project newProject(name, basePath);
if (newProject.create()) {
projectManager.currentProject = newProject;
projectManager.addToRecentProjects(name,
(newProject.projectPath / "project.modu").string());
if (!initRenderer()) {
logToConsole("Error: Failed to initialize renderer!");
return;
}
sceneObjects.clear();
selectedObjectId = -1;
nextObjectId = 0;
addObject(ObjectType::Cube, "Cube");
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true;
showLauncher = false;
firstFrame = true;
addConsoleMessage("Created new project: " + std::string(name), ConsoleMessageType::Success);
addConsoleMessage("Project location: " + newProject.projectPath.string(), ConsoleMessageType::Info);
saveCurrentScene();
} else {
projectManager.errorMessage = "Failed to create project directory";
}
}
void Engine::loadRecentScenes() {
sceneObjects.clear();
selectedObjectId = -1;
nextObjectId = 0;
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
if (fs::exists(scenePath)) {
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
addConsoleMessage("Loaded scene: " + projectManager.currentProject.currentSceneName, ConsoleMessageType::Success);
} else {
addConsoleMessage("Warning: Failed to load scene, starting fresh", ConsoleMessageType::Warning);
addObject(ObjectType::Cube, "Cube");
}
} else {
addConsoleMessage("Default scene not found, starting with a new scene.", ConsoleMessageType::Info);
addObject(ObjectType::Cube, "Cube");
}
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true;
}
void Engine::saveCurrentScene() {
if (!projectManager.currentProject.isLoaded) return;
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
if (SceneSerializer::saveScene(scenePath, sceneObjects, nextObjectId)) {
projectManager.currentProject.hasUnsavedChanges = false;
projectManager.currentProject.saveProjectFile();
addConsoleMessage("Saved scene: " + projectManager.currentProject.currentSceneName, ConsoleMessageType::Success);
} else {
addConsoleMessage("Error: Failed to save scene!", ConsoleMessageType::Error);
}
}
void Engine::loadScene(const std::string& sceneName) {
if (!projectManager.currentProject.isLoaded) return;
if (projectManager.currentProject.hasUnsavedChanges) {
saveCurrentScene();
}
fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName);
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
projectManager.currentProject.currentSceneName = sceneName;
projectManager.currentProject.hasUnsavedChanges = false;
projectManager.currentProject.saveProjectFile();
selectedObjectId = -1;
addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success);
} else {
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
}
}
void Engine::createNewScene(const std::string& sceneName) {
if (!projectManager.currentProject.isLoaded || sceneName.empty()) return;
if (projectManager.currentProject.hasUnsavedChanges) {
saveCurrentScene();
}
sceneObjects.clear();
selectedObjectId = -1;
nextObjectId = 0;
projectManager.currentProject.currentSceneName = sceneName;
projectManager.currentProject.hasUnsavedChanges = true;
addObject(ObjectType::Cube, "Cube");
saveCurrentScene();
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
}
void Engine::addObject(ObjectType type, const std::string& baseName) {
int id = nextObjectId++;
std::string name = baseName + " " + std::to_string(id);
sceneObjects.push_back(SceneObject(name, type, id));
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
logToConsole("Created: " + name);
}
void Engine::duplicateSelected() {
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
if (it != sceneObjects.end()) {
int id = nextObjectId++;
SceneObject newObj(it->name + " (Copy)", it->type, id);
newObj.position = it->position + glm::vec3(1.0f, 0.0f, 0.0f);
newObj.rotation = it->rotation;
newObj.scale = it->scale;
newObj.meshPath = it->meshPath;
newObj.meshId = it->meshId;
sceneObjects.push_back(newObj);
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
logToConsole("Duplicated: " + newObj.name);
}
}
void Engine::deleteSelected() {
auto it = std::remove_if(sceneObjects.begin(), sceneObjects.end(),
[this](const SceneObject& obj) { return obj.id == selectedObjectId; });
if (it != sceneObjects.end()) {
logToConsole("Deleted object");
sceneObjects.erase(it, sceneObjects.end());
selectedObjectId = -1;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
}
}
void Engine::setParent(int childId, int parentId) {
auto childIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[childId](const SceneObject& obj) { return obj.id == childId; });
if (childIt == sceneObjects.end()) return;
if (childIt->parentId != -1) {
auto oldParentIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[&childIt](const SceneObject& obj) { return obj.id == childIt->parentId; });
if (oldParentIt != sceneObjects.end()) {
auto& children = oldParentIt->childIds;
children.erase(std::remove(children.begin(), children.end(), childId), children.end());
}
}
childIt->parentId = parentId;
if (parentId != -1) {
auto newParentIt = std::find_if(sceneObjects.begin(), sceneObjects.end(),
[parentId](const SceneObject& obj) { return obj.id == parentId; });
if (newParentIt != sceneObjects.end()) {
newParentIt->childIds.push_back(childId);
}
}
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
logToConsole("Reparented object");
}
void Engine::addConsoleMessage(const std::string& message, ConsoleMessageType type) {
std::string prefix;
switch (type) {
case ConsoleMessageType::Info: prefix = "Info: "; break;
case ConsoleMessageType::Warning: prefix = "Warning: "; break;
case ConsoleMessageType::Error: prefix = "Error: "; break;
case ConsoleMessageType::Success: prefix = "Success: "; break;
}
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::string timeStr = std::ctime(&time);
timeStr = timeStr.substr(11, 8);
consoleLog.push_back("[" + timeStr + "] " + prefix + " " + message);
if (consoleLog.size() > 1000) {
consoleLog.erase(consoleLog.begin());
}
}
void Engine::logToConsole(const std::string& message) {
addConsoleMessage(message, ConsoleMessageType::Info);
}
void Engine::setupImGui() {
std::cerr << "[DEBUG] setupImGui: getting primary monitor..." << std::endl;
float mainScale = 1.0f;
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor) {
std::cerr << "[DEBUG] setupImGui: got primary monitor, getting content scale..." << std::endl;
mainScale = ImGui_ImplGlfw_GetContentScaleForMonitor(primaryMonitor);
std::cerr << "[DEBUG] setupImGui: content scale = " << mainScale << std::endl;
} else {
std::cerr << "[DEBUG] setupImGui: WARNING - no primary monitor found!" << std::endl;
}
std::cerr << "[DEBUG] setupImGui: creating ImGui context..." << std::endl;
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
#ifndef __linux__
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
#endif
std::cerr << "[DEBUG] setupImGui: applying theme..." << std::endl;
applyModernTheme();
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(mainScale);
style.FontScaleDpi = mainScale;
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
std::cerr << "[DEBUG] setupImGui: initializing ImGui GLFW backend..." << std::endl;
ImGui_ImplGlfw_InitForOpenGL(editorWindow, true);
std::cerr << "[DEBUG] setupImGui: initializing ImGui OpenGL3 backend..." << std::endl;
if (!ImGui_ImplOpenGL3_Init("#version 330")) {
std::cerr << "[DEBUG] ImGui OpenGL3 init failed!" << std::endl;
throw std::runtime_error("ImGui error");
}
std::cerr << "[DEBUG] setupImGui: complete!" << std::endl;
}

124
src/Engine.h Normal file
View File

@@ -0,0 +1,124 @@
#pragma once
#include "Common.h"
#include "SceneObject.h"
#include "Camera.h"
#include "Rendering.h"
#include "ProjectManager.h"
#include "EditorUI.h"
#include "../include/Window/Window.h"
void window_size_callback(GLFWwindow* window, int width, int height);
class Engine {
private:
Window window;
GLFWwindow* editorWindow = nullptr;
Renderer renderer;
Camera camera;
ViewportController viewportController;
float deltaTime = 0.0f;
float lastFrame = 0.0f;
bool cursorLocked = false;
int viewportWidth = 800;
int viewportHeight = 600;
std::vector<SceneObject> sceneObjects;
int selectedObjectId = -1;
int nextObjectId = 0;
// Gizmo state
ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::LOCAL;
bool useSnap = false;
float snapValue[3] = { 0.5f, 0.5f, 0.5f };
float rotationSnapValue = 15.0f;
FileBrowser fileBrowser;
bool viewportFullscreen = false;
bool showHierarchy = true;
bool showInspector = true;
bool showFileBrowser = true;
bool showConsole = true;
bool showProjectBrowser = true; // Now merged into file browser
bool firstFrame = true;
std::vector<std::string> consoleLog;
int draggedObjectId = -1;
ProjectManager projectManager;
bool showLauncher = true;
bool showNewSceneDialog = false;
bool showSaveSceneAsDialog = false;
char newSceneName[128] = "";
char saveSceneAsName[128] = "";
bool rendererInitialized = false;
bool showImportOBJDialog = false;
bool showImportModelDialog = false; // For Assimp models
std::string pendingOBJPath;
std::string pendingModelPath; // For Assimp models
char importOBJName[128] = "";
char importModelName[128] = ""; // For Assimp models
char fileBrowserSearch[256] = "";
// Private methods
SceneObject* getSelectedObject();
static void DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale);
void importOBJToScene(const std::string& filepath, const std::string& objectName);
void importModelToScene(const std::string& filepath, const std::string& objectName); // Assimp import
void handleKeyboardShortcuts();
void OpenProjectPath(const std::string& path);
// UI rendering methods
void renderLauncher();
void renderNewProjectDialog();
void renderOpenProjectDialog();
void renderMainMenuBar();
void renderHierarchyPanel();
void renderObjectNode(SceneObject& obj, const std::string& filter);
void renderFileBrowserPanel();
void renderInspectorPanel();
void renderConsolePanel();
void renderViewport();
void renderDialogs();
void renderProjectBrowserPanel();
void renderFileBrowserToolbar();
void renderFileBrowserBreadcrumb();
void renderFileBrowserGridView();
void renderFileBrowserListView();
void renderFileContextMenu(const fs::directory_entry& entry);
void handleFileDoubleClick(const fs::directory_entry& entry);
ImVec4 getFileCategoryColor(FileCategory category) const;
const char* getFileCategoryIconText(FileCategory category) const;
// Project/scene management
void createNewProject(const char* name, const char* location);
void loadRecentScenes();
void saveCurrentScene();
void loadScene(const std::string& sceneName);
void createNewScene(const std::string& sceneName);
// Scene object management
void addObject(ObjectType type, const std::string& baseName);
void duplicateSelected();
void deleteSelected();
void setParent(int childId, int parentId);
// Console/logging
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
void logToConsole(const std::string& message);
// ImGui setup
void setupImGui();
bool initRenderer();
public:
Engine() = default;
bool init();
void run();
void shutdown();
};

1132
src/EnginePanels.cpp Normal file

File diff suppressed because it is too large Load Diff

242
src/ModelLoader.cpp Normal file
View File

@@ -0,0 +1,242 @@
#include "ModelLoader.h"
#include <algorithm>
#include <iostream>
ModelLoader& ModelLoader::getInstance() {
static ModelLoader instance;
return instance;
}
ModelLoader& getModelLoader() {
return ModelLoader::getInstance();
}
std::vector<ModelFormat> ModelLoader::getSupportedFormats() {
return {
{".fbx", "Autodesk FBX", true},
{".obj", "Wavefront OBJ", false},
{".gltf", "glTF 2.0", true},
{".glb", "glTF Binary", true},
{".dae", "Collada", true},
{".blend", "Blender", true},
{".3ds", "3D Studio Max", false},
{".ase", "3D Studio ASE", false},
{".ifc", "IFC-STEP", false},
{".xgl", "XGL", false},
{".zgl", "ZGL", false},
{".ply", "Stanford PLY", false},
{".dxf", "AutoCAD DXF", false},
{".lwo", "LightWave", false},
{".lws", "LightWave Scene", false},
{".lxo", "Modo", false},
{".stl", "Stereolithography", false},
{".x", "DirectX X", true},
{".ac", "AC3D", false},
{".ms3d", "Milkshape 3D", true},
{".cob", "TrueSpace", false},
{".scn", "TrueSpace Scene", false},
{".bvh", "Biovision BVH", true},
{".csm", "CharacterStudio Motion", true},
{".irrmesh", "Irrlicht Mesh", false},
{".irr", "Irrlicht Scene", false},
{".mdl", "Quake MDL", true},
{".md2", "Quake II MD2", true},
{".md3", "Quake III MD3", true},
{".pk3", "Quake III BSP", false},
{".mdc", "RtCW MDC", true},
{".md5mesh", "Doom 3 MD5", true},
{".md5anim", "Doom 3 MD5 Anim", true},
{".smd", "Valve SMD", true},
{".vta", "Valve VTA", false},
{".ogex", "Open Game Engine Exchange", true},
{".3d", "Unreal 3D", false},
{".b3d", "BlitzBasic 3D", true},
{".q3d", "Quick3D", false},
{".q3s", "Quick3D Scene", false},
{".nff", "Neutral File Format", false},
{".off", "Object File Format", false},
{".raw", "Raw Triangles", false},
{".ter", "Terragen Terrain", false},
{".hmp", "3D GameStudio HMP", false},
{".ndo", "Nendo", false},
};
}
bool ModelLoader::isSupported(const std::string& filepath) const {
std::string ext = fs::path(filepath).extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
auto formats = getSupportedFormats();
for (const auto& format : formats) {
if (format.extension == ext) {
return true;
}
}
return false;
}
ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
ModelLoadResult result;
// Check if already loaded
for (size_t i = 0; i < loadedMeshes.size(); i++) {
if (loadedMeshes[i].path == filepath) {
result.success = true;
result.meshIndex = static_cast<int>(i);
const auto& mesh = loadedMeshes[i];
result.vertexCount = mesh.vertexCount;
result.faceCount = mesh.faceCount;
result.hasNormals = mesh.hasNormals;
result.hasTexCoords = mesh.hasTexCoords;
return result;
}
}
// Check if format is supported
if (!isSupported(filepath)) {
result.errorMessage = "Unsupported file format: " + fs::path(filepath).extension().string();
return result;
}
// Configure import flags
unsigned int importFlags =
aiProcess_Triangulate | // Convert all faces to triangles
aiProcess_GenSmoothNormals | // Generate smooth normals if missing
aiProcess_FlipUVs | // Flip UV coordinates for OpenGL
aiProcess_CalcTangentSpace | // Calculate tangents and bitangents
aiProcess_JoinIdenticalVertices | // Optimize vertex count
aiProcess_SortByPType | // Sort by primitive type
aiProcess_OptimizeMeshes | // Reduce number of meshes
aiProcess_ValidateDataStructure; // Validate the imported data
// Load the model
const aiScene* scene = importer.ReadFile(filepath, importFlags);
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
result.errorMessage = "Assimp error: " + std::string(importer.GetErrorString());
return result;
}
// Process all meshes in the scene
std::vector<float> vertices;
result.meshCount = scene->mNumMeshes;
result.hasNormals = true;
result.hasTexCoords = false;
result.hasTangents = false;
// Process the root node recursively
processNode(scene->mRootNode, scene, vertices);
// Check mesh properties
for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[i];
result.meshNames.push_back(mesh->mName.C_Str());
result.vertexCount += mesh->mNumVertices;
result.faceCount += mesh->mNumFaces;
if (mesh->mTextureCoords[0]) result.hasTexCoords = true;
if (mesh->mTangents) result.hasTangents = true;
}
if (vertices.empty()) {
result.errorMessage = "No vertices found in model file";
return result;
}
// Create the mesh
OBJLoader::LoadedMesh loaded;
loaded.path = filepath;
loaded.name = fs::path(filepath).stem().string();
loaded.mesh = std::make_unique<Mesh>(vertices.data(), vertices.size() * sizeof(float));
loaded.vertexCount = result.vertexCount;
loaded.faceCount = result.faceCount;
loaded.hasNormals = result.hasNormals;
loaded.hasTexCoords = result.hasTexCoords;
loadedMeshes.push_back(std::move(loaded));
result.success = true;
result.meshIndex = static_cast<int>(loadedMeshes.size() - 1);
std::cerr << "[ModelLoader] Loaded " << filepath << " with "
<< result.vertexCount << " vertices, "
<< result.faceCount << " faces, "
<< result.meshCount << " meshes" << std::endl;
return result;
}
void ModelLoader::processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices) {
// Process all meshes in this node
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
processMesh(mesh, scene, vertices);
}
// Process children nodes
for (unsigned int i = 0; i < node->mNumChildren; i++) {
processNode(node->mChildren[i], scene, vertices);
}
}
void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices) {
// Process each face
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
aiFace face = mesh->mFaces[i];
// Process each vertex of the face
for (unsigned int j = 0; j < face.mNumIndices; j++) {
unsigned int index = face.mIndices[j];
// Position
vertices.push_back(mesh->mVertices[index].x);
vertices.push_back(mesh->mVertices[index].y);
vertices.push_back(mesh->mVertices[index].z);
// Normal
if (mesh->mNormals) {
vertices.push_back(mesh->mNormals[index].x);
vertices.push_back(mesh->mNormals[index].y);
vertices.push_back(mesh->mNormals[index].z);
} else {
vertices.push_back(0.0f);
vertices.push_back(1.0f);
vertices.push_back(0.0f);
}
// Texture coordinates
if (mesh->mTextureCoords[0]) {
vertices.push_back(mesh->mTextureCoords[0][index].x);
vertices.push_back(mesh->mTextureCoords[0][index].y);
} else {
vertices.push_back(0.0f);
vertices.push_back(0.0f);
}
}
}
}
Mesh* ModelLoader::getMesh(int index) {
if (index < 0 || index >= static_cast<int>(loadedMeshes.size())) {
return nullptr;
}
return loadedMeshes[index].mesh.get();
}
const OBJLoader::LoadedMesh* ModelLoader::getMeshInfo(int index) const {
if (index < 0 || index >= static_cast<int>(loadedMeshes.size())) {
return nullptr;
}
return &loadedMeshes[index];
}
const std::vector<OBJLoader::LoadedMesh>& ModelLoader::getAllMeshes() const {
return loadedMeshes;
}
void ModelLoader::clear() {
loadedMeshes.clear();
}
size_t ModelLoader::getMeshCount() const {
return loadedMeshes.size();
}

78
src/ModelLoader.h Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include "Common.h"
#include "Rendering.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
// Supported file extensions for model import
struct ModelFormat {
std::string extension;
std::string description;
bool supportsAnimation;
};
// Model loading result with detailed info
struct ModelLoadResult {
bool success = false;
int meshIndex = -1;
std::string errorMessage;
int vertexCount = 0;
int faceCount = 0;
int meshCount = 0;
bool hasNormals = false;
bool hasTexCoords = false;
bool hasTangents = false;
std::vector<std::string> meshNames;
};
class ModelLoader {
public:
// Singleton access
static ModelLoader& getInstance();
// Load a model file (FBX, OBJ, GLTF, etc.)
ModelLoadResult loadModel(const std::string& filepath);
// Get mesh by index
Mesh* getMesh(int index);
// Get mesh info
const OBJLoader::LoadedMesh* getMeshInfo(int index) const;
// Get all loaded meshes
const std::vector<OBJLoader::LoadedMesh>& getAllMeshes() const;
// Check if file extension is supported
bool isSupported(const std::string& filepath) const;
// Get list of supported formats
static std::vector<ModelFormat> getSupportedFormats();
// Clear all loaded meshes
void clear();
// Get mesh count
size_t getMeshCount() const;
private:
ModelLoader() = default;
~ModelLoader() = default;
ModelLoader(const ModelLoader&) = delete;
ModelLoader& operator=(const ModelLoader&) = delete;
// Process Assimp scene
void processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices);
void processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices);
// Storage for loaded meshes (reusing OBJLoader::LoadedMesh structure)
std::vector<OBJLoader::LoadedMesh> loadedMeshes;
// Assimp importer (kept for resource management)
Assimp::Importer importer;
};
// Global accessor
ModelLoader& getModelLoader();

352
src/ProjectManager.cpp Normal file
View File

@@ -0,0 +1,352 @@
#include "ProjectManager.h"
#include "Rendering.h"
// Project implementation
Project::Project(const std::string& projectName, const fs::path& basePath)
: name(projectName) {
projectPath = basePath / projectName;
scenesPath = projectPath / "Scenes";
assetsPath = projectPath / "Assets";
scriptsPath = projectPath / "Scripts";
}
bool Project::create() {
try {
fs::create_directories(projectPath);
fs::create_directories(scenesPath);
fs::create_directories(assetsPath);
fs::create_directories(assetsPath / "Textures");
fs::create_directories(assetsPath / "Models");
fs::create_directories(assetsPath / "Shaders");
fs::create_directories(scriptsPath);
saveProjectFile();
currentSceneName = "Main";
isLoaded = true;
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to create project: " << e.what() << std::endl;
return false;
}
}
bool Project::load(const fs::path& projectFilePath) {
try {
projectPath = projectFilePath.parent_path();
scenesPath = projectPath / "Scenes";
assetsPath = projectPath / "Assets";
scriptsPath = projectPath / "Scripts";
std::ifstream file(projectFilePath);
if (!file.is_open()) return false;
std::string line;
while (std::getline(file, line)) {
if (line.find("name=") == 0) {
name = line.substr(5);
} else if (line.find("lastScene=") == 0) {
currentSceneName = line.substr(10);
}
}
file.close();
if (currentSceneName.empty()) {
currentSceneName = "Main";
}
isLoaded = true;
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to load project: " << e.what() << std::endl;
return false;
}
}
void Project::saveProjectFile() const {
std::ofstream file(projectPath / "project.modu");
file << "name=" << name << "\n";
file << "lastScene=" << currentSceneName << "\n";
file.close();
}
std::vector<std::string> Project::getSceneList() const {
std::vector<std::string> scenes;
try {
for (const auto& entry : fs::directory_iterator(scenesPath)) {
if (entry.path().extension() == ".scene") {
scenes.push_back(entry.path().stem().string());
}
}
} catch (...) {}
return scenes;
}
fs::path Project::getSceneFilePath(const std::string& sceneName) const {
return scenesPath / (sceneName + ".scene");
}
// ProjectManager implementation
ProjectManager::ProjectManager() {
#ifdef _WIN32
const char* appdata = std::getenv("APPDATA");
if (appdata) {
appDataPath = fs::path(appdata) / ".Modularity";
} else {
appDataPath = fs::current_path() / "AppData";
}
#else
const char* home = std::getenv("HOME");
if (home) {
appDataPath = fs::path(home) / ".Modularity";
} else {
appDataPath = fs::current_path() / ".Modularity";
}
#endif
fs::create_directories(appDataPath);
loadRecentProjects();
std::string defaultPath = (fs::current_path() / "Projects").string();
strncpy(newProjectLocation, defaultPath.c_str(), sizeof(newProjectLocation) - 1);
}
void ProjectManager::loadRecentProjects() {
recentProjects.clear();
fs::path recentFile = appDataPath / "recent_projects.txt";
std::cerr << "[DEBUG] Loading recent projects from: " << recentFile << std::endl;
if (!fs::exists(recentFile)) {
std::cerr << "[DEBUG] Recent projects file does not exist" << std::endl;
return;
}
std::ifstream file(recentFile);
std::string line;
int lineNum = 0;
while (std::getline(file, line)) {
lineNum++;
if (line.empty()) continue;
line.erase(0, line.find_first_not_of(" \t\r\n"));
line.erase(line.find_last_not_of(" \t\r\n") + 1);
if (line.empty()) continue;
RecentProject rp;
size_t pos1 = line.find('|');
size_t pos2 = line.find('|', pos1 + 1);
if (pos1 != std::string::npos && pos2 != std::string::npos) {
rp.name = line.substr(0, pos1);
rp.path = line.substr(pos1 + 1, pos2 - pos1 - 1);
rp.lastOpened = line.substr(pos2 + 1);
rp.path.erase(0, rp.path.find_first_not_of(" \t\r\n"));
rp.path.erase(rp.path.find_last_not_of(" \t\r\n") + 1);
std::cerr << "[DEBUG] Line " << lineNum << ": name='" << rp.name
<< "' path='" << rp.path << "' exists=" << fs::exists(rp.path) << std::endl;
if (fs::exists(rp.path)) {
recentProjects.push_back(rp);
} else {
std::cerr << "[DEBUG] Project path does not exist, skipping: " << rp.path << std::endl;
}
} else {
std::cerr << "[DEBUG] Line " << lineNum << " malformed: " << line << std::endl;
}
}
file.close();
std::cerr << "[DEBUG] Loaded " << recentProjects.size() << " recent projects" << std::endl;
}
void ProjectManager::saveRecentProjects() {
fs::path recentFile = appDataPath / "recent_projects.txt";
std::cerr << "[DEBUG] Saving recent projects to: " << recentFile << std::endl;
std::ofstream file(recentFile);
for (const auto& rp : recentProjects) {
std::string absolutePath = rp.path;
try {
if (fs::exists(rp.path)) {
absolutePath = fs::canonical(rp.path).string();
}
} catch (...) {
// Keep original path if canonical fails
}
file << rp.name << "|" << absolutePath << "|" << rp.lastOpened << "\n";
std::cerr << "[DEBUG] Saved: " << rp.name << " -> " << absolutePath << std::endl;
}
file.close();
}
void ProjectManager::addToRecentProjects(const std::string& name, const std::string& path) {
std::string absolutePath = path;
try {
if (fs::exists(path)) {
absolutePath = fs::canonical(path).string();
} else {
// For new projects, the file might not exist yet - use absolute()
absolutePath = fs::absolute(path).string();
}
} catch (...) {
// Keep original path if conversion fails
}
std::cerr << "[DEBUG] Adding to recent: " << name << " -> " << absolutePath << std::endl;
recentProjects.erase(
std::remove_if(recentProjects.begin(), recentProjects.end(),
[&absolutePath](const RecentProject& rp) { return rp.path == absolutePath; }),
recentProjects.end()
);
std::time_t now = std::time(nullptr);
char timeStr[64];
std::strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M", std::localtime(&now));
RecentProject rp;
rp.name = name;
rp.path = absolutePath; // Use absolute path
rp.lastOpened = timeStr;
recentProjects.insert(recentProjects.begin(), rp);
saveRecentProjects();
}
bool ProjectManager::loadProject(const std::string& path) {
if (currentProject.load(path)) {
addToRecentProjects(currentProject.name, path);
return true;
}
errorMessage = "Failed to load project file";
return false;
}
// SceneSerializer implementation
bool SceneSerializer::saveScene(const fs::path& filePath,
const std::vector<SceneObject>& objects,
int nextId) {
try {
std::ofstream file(filePath);
if (!file.is_open()) return false;
file << "# Scene File\n";
file << "version=2\n";
file << "nextId=" << nextId << "\n";
file << "objectCount=" << objects.size() << "\n";
file << "\n";
for (const auto& obj : objects) {
file << "[Object]\n";
file << "id=" << obj.id << "\n";
file << "name=" << obj.name << "\n";
file << "type=" << static_cast<int>(obj.type) << "\n";
file << "parentId=" << obj.parentId << "\n";
file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n";
file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n";
if (obj.type == ObjectType::OBJMesh && !obj.meshPath.empty()) {
file << "meshPath=" << obj.meshPath << "\n";
}
file << "children=";
for (size_t i = 0; i < obj.childIds.size(); i++) {
if (i > 0) file << ",";
file << obj.childIds[i];
}
file << "\n\n";
}
file.close();
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to save scene: " << e.what() << std::endl;
return false;
}
}
bool SceneSerializer::loadScene(const fs::path& filePath,
std::vector<SceneObject>& objects,
int& nextId) {
try {
std::ifstream file(filePath);
if (!file.is_open()) return false;
objects.clear();
std::string line;
SceneObject* currentObj = nullptr;
while (std::getline(file, line)) {
line.erase(0, line.find_first_not_of(" \t\r\n"));
line.erase(line.find_last_not_of(" \t\r\n") + 1);
if (line.empty() || line[0] == '#') continue;
if (line == "[Object]") {
objects.push_back(SceneObject("", ObjectType::Cube, 0));
currentObj = &objects.back();
continue;
}
size_t eqPos = line.find('=');
if (eqPos == std::string::npos) continue;
std::string key = line.substr(0, eqPos);
std::string value = line.substr(eqPos + 1);
if (key == "nextId") {
nextId = std::stoi(value);
} else if (currentObj) {
if (key == "id") {
currentObj->id = std::stoi(value);
} else if (key == "name") {
currentObj->name = value;
} else if (key == "type") {
currentObj->type = static_cast<ObjectType>(std::stoi(value));
} else if (key == "parentId") {
currentObj->parentId = std::stoi(value);
} else if (key == "position") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->position.x,
&currentObj->position.y,
&currentObj->position.z);
} else if (key == "rotation") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->rotation.x,
&currentObj->rotation.y,
&currentObj->rotation.z);
} else if (key == "scale") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->scale.x,
&currentObj->scale.y,
&currentObj->scale.z);
} else if (key == "meshPath") {
currentObj->meshPath = value;
if (!value.empty() && currentObj->type == ObjectType::OBJMesh) {
std::string err;
currentObj->meshId = g_objLoader.loadOBJ(value, err);
}
} else if (key == "children" && !value.empty()) {
std::stringstream ss(value);
std::string item;
while (std::getline(ss, item, ',')) {
if (!item.empty()) {
currentObj->childIds.push_back(std::stoi(item));
}
}
}
}
}
file.close();
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to load scene: " << e.what() << std::endl;
return false;
}
}

62
src/ProjectManager.h Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include "Common.h"
#include "SceneObject.h"
struct RecentProject {
std::string name;
std::string path;
std::string lastOpened;
};
class Project {
public:
std::string name;
fs::path projectPath;
fs::path scenesPath;
fs::path assetsPath;
fs::path scriptsPath;
std::string currentSceneName;
bool isLoaded = false;
bool hasUnsavedChanges = false;
Project() = default;
Project(const std::string& projectName, const fs::path& basePath);
bool create();
bool load(const fs::path& projectFilePath);
void saveProjectFile() const;
std::vector<std::string> getSceneList() const;
fs::path getSceneFilePath(const std::string& sceneName) const;
};
class ProjectManager {
public:
std::vector<RecentProject> recentProjects;
fs::path appDataPath;
char newProjectName[128] = "";
char newProjectLocation[512] = "";
char openProjectPath[512] = "";
bool showNewProjectDialog = false;
bool showOpenProjectDialog = false;
std::string errorMessage;
Project currentProject;
ProjectManager();
void loadRecentProjects();
void saveRecentProjects();
void addToRecentProjects(const std::string& name, const std::string& path);
bool loadProject(const std::string& path);
};
class SceneSerializer {
public:
static bool saveScene(const fs::path& filePath,
const std::vector<SceneObject>& objects,
int nextId);
static bool loadScene(const fs::path& filePath,
std::vector<SceneObject>& objects,
int& nextId);
};

587
src/Rendering.cpp Normal file
View File

@@ -0,0 +1,587 @@
#include "Rendering.h"
#include "Camera.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include "../include/ThirdParty/tiny_obj_loader.h"
// Global OBJ loader instance
OBJLoader g_objLoader;
// Cube vertex data
float vertices[] = {
// Back face (z = -0.5f)
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
// Front face (z = 0.5f)
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
// Left face (x = -0.5f)
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
// Right face (x = 0.5f)
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
// Bottom face (y = -0.5f)
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
// Top face (y = 0.5f)
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
std::vector<float> generateSphere(int segments, int rings) {
std::vector<float> vertices;
for (int ring = 0; ring <= rings; ring++) {
float theta = ring * PI / rings;
float sinTheta = sin(theta);
float cosTheta = cos(theta);
for (int seg = 0; seg <= segments; seg++) {
float phi = seg * 2.0f * PI / segments;
float sinPhi = sin(phi);
float cosPhi = cos(phi);
float x = cosPhi * sinTheta;
float y = cosTheta;
float z = sinPhi * sinTheta;
// Position
vertices.push_back(x * 0.5f);
vertices.push_back(y * 0.5f);
vertices.push_back(z * 0.5f);
// Normal (same as position for unit sphere)
vertices.push_back(x);
vertices.push_back(y);
vertices.push_back(z);
// Texcoord
vertices.push_back((float)seg / segments);
vertices.push_back((float)ring / rings);
}
}
std::vector<float> triangulated;
int stride = segments + 1;
for (int ring = 0; ring < rings; ring++) {
for (int seg = 0; seg < segments; seg++) {
int current = ring * stride + seg;
int next = current + stride;
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[current * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[next * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(current + 1) * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(current + 1) * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[next * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(next + 1) * 8 + i]);
}
}
return triangulated;
}
std::vector<float> generateCapsule(int segments, int rings) {
std::vector<float> vertices;
float cylinderHeight = 0.5f;
float radius = 0.25f;
// Top hemisphere
for (int ring = 0; ring <= rings / 2; ring++) {
float theta = ring * PI / rings;
float sinTheta = sin(theta);
float cosTheta = cos(theta);
for (int seg = 0; seg <= segments; seg++) {
float phi = seg * 2.0f * PI / segments;
float sinPhi = sin(phi);
float cosPhi = cos(phi);
float x = cosPhi * sinTheta * radius;
float y = cosTheta * radius + cylinderHeight;
float z = sinPhi * sinTheta * radius;
vertices.push_back(x);
vertices.push_back(y);
vertices.push_back(z);
glm::vec3 normal = glm::normalize(glm::vec3(x, y - cylinderHeight, z));
vertices.push_back(normal.x);
vertices.push_back(normal.y);
vertices.push_back(normal.z);
vertices.push_back((float)seg / segments);
vertices.push_back((float)ring / (rings / 2));
}
}
// Cylinder body
for (int i = 0; i <= 1; i++) {
float y = i == 0 ? cylinderHeight : -cylinderHeight;
for (int seg = 0; seg <= segments; seg++) {
float phi = seg * 2.0f * PI / segments;
float x = cos(phi) * radius;
float z = sin(phi) * radius;
vertices.push_back(x);
vertices.push_back(y);
vertices.push_back(z);
glm::vec3 normal = glm::normalize(glm::vec3(x, 0.0f, z));
vertices.push_back(normal.x);
vertices.push_back(normal.y);
vertices.push_back(normal.z);
vertices.push_back((float)seg / segments);
vertices.push_back(0.5f);
}
}
// Bottom hemisphere
for (int ring = rings / 2; ring <= rings; ring++) {
float theta = ring * PI / rings;
float sinTheta = sin(theta);
float cosTheta = cos(theta);
for (int seg = 0; seg <= segments; seg++) {
float phi = seg * 2.0f * PI / segments;
float sinPhi = sin(phi);
float cosPhi = cos(phi);
float x = cosPhi * sinTheta * radius;
float y = cosTheta * radius - cylinderHeight;
float z = sinPhi * sinTheta * radius;
vertices.push_back(x);
vertices.push_back(y);
vertices.push_back(z);
glm::vec3 normal = glm::normalize(glm::vec3(x, y + cylinderHeight, z));
vertices.push_back(normal.x);
vertices.push_back(normal.y);
vertices.push_back(normal.z);
vertices.push_back((float)seg / segments);
vertices.push_back((float)ring / rings);
}
}
std::vector<float> triangulated;
int stride = segments + 1;
int totalRings = rings + 3;
for (int ring = 0; ring < totalRings - 1; ring++) {
for (int seg = 0; seg < segments; seg++) {
int current = ring * stride + seg;
int next = current + stride;
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[current * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[next * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(current + 1) * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(current + 1) * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[next * 8 + i]);
for (int i = 0; i < 8; i++) triangulated.push_back(vertices[(next + 1) * 8 + i]);
}
}
return triangulated;
}
// Mesh implementation
Mesh::Mesh(const float* vertexData, size_t dataSizeBytes) {
vertexCount = dataSizeBytes / (8 * sizeof(float));
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, dataSizeBytes, vertexData, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Mesh::~Mesh() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
}
void Mesh::draw() const {
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
glBindVertexArray(0);
}
// OBJLoader implementation
int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
// Check if already loaded
for (size_t i = 0; i < loadedMeshes.size(); i++) {
if (loadedMeshes[i].path == filepath) {
return static_cast<int>(i);
}
}
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn, err;
std::string baseDir = fs::path(filepath).parent_path().string();
if (!baseDir.empty()) baseDir += "/";
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err,
filepath.c_str(), baseDir.c_str());
if (!warn.empty()) {
errorMsg += "Warning: " + warn + "\n";
}
if (!err.empty()) {
errorMsg += "Error: " + err + "\n";
}
if (!ret || shapes.empty()) {
errorMsg += "Failed to load OBJ file: " + filepath;
return -1;
}
std::vector<float> vertices;
bool hasNormalsInFile = !attrib.normals.empty();
int faceCount = 0;
for (const auto& shape : shapes) {
faceCount += static_cast<int>(shape.mesh.num_face_vertices.size());
}
for (const auto& shape : shapes) {
size_t indexOffset = 0;
for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); f++) {
int fv = shape.mesh.num_face_vertices[f];
struct TempVertex {
glm::vec3 pos;
glm::vec2 uv;
glm::vec3 normal;
bool hasNormal = false;
};
std::vector<TempVertex> faceVerts;
for (int v = 0; v < fv; v++) {
tinyobj::index_t idx = shape.mesh.indices[indexOffset + v];
TempVertex tv;
tv.pos.x = attrib.vertices[3 * size_t(idx.vertex_index) + 0];
tv.pos.y = attrib.vertices[3 * size_t(idx.vertex_index) + 1];
tv.pos.z = attrib.vertices[3 * size_t(idx.vertex_index) + 2];
if (idx.texcoord_index >= 0 && !attrib.texcoords.empty()) {
tv.uv.x = attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
tv.uv.y = attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
} else {
tv.uv = glm::vec2(0.0f);
}
if (idx.normal_index >= 0 && hasNormalsInFile) {
tv.normal.x = attrib.normals[3 * size_t(idx.normal_index) + 0];
tv.normal.y = attrib.normals[3 * size_t(idx.normal_index) + 1];
tv.normal.z = attrib.normals[3 * size_t(idx.normal_index) + 2];
tv.hasNormal = true;
}
faceVerts.push_back(tv);
}
if (!hasNormalsInFile && fv >= 3) {
glm::vec3 v0 = faceVerts[0].pos;
glm::vec3 v1 = faceVerts[1].pos;
glm::vec3 v2 = faceVerts[2].pos;
glm::vec3 faceNormal = glm::normalize(glm::cross(v1 - v0, v2 - v0));
for (auto& tv : faceVerts) {
tv.normal = faceNormal;
tv.hasNormal = true;
}
}
for (int v = 1; v < fv - 1; v++) {
const TempVertex* tri[3] = { &faceVerts[0], &faceVerts[v], &faceVerts[v+1] };
for (int i = 0; i < 3; i++) {
vertices.push_back(tri[i]->pos.x);
vertices.push_back(tri[i]->pos.y);
vertices.push_back(tri[i]->pos.z);
vertices.push_back(tri[i]->normal.x);
vertices.push_back(tri[i]->normal.y);
vertices.push_back(tri[i]->normal.z);
vertices.push_back(tri[i]->uv.x);
vertices.push_back(tri[i]->uv.y);
}
}
indexOffset += fv;
}
}
if (vertices.empty()) {
errorMsg += "No vertices found in OBJ file";
return -1;
}
LoadedMesh loaded;
loaded.path = filepath;
loaded.name = fs::path(filepath).stem().string();
loaded.mesh = std::make_unique<Mesh>(vertices.data(), vertices.size() * sizeof(float));
loaded.vertexCount = static_cast<int>(vertices.size() / 8);
loaded.faceCount = faceCount;
loaded.hasNormals = hasNormalsInFile;
loaded.hasTexCoords = !attrib.texcoords.empty();
loadedMeshes.push_back(std::move(loaded));
return static_cast<int>(loadedMeshes.size() - 1);
}
Mesh* OBJLoader::getMesh(int index) {
if (index < 0 || index >= static_cast<int>(loadedMeshes.size())) {
return nullptr;
}
return loadedMeshes[index].mesh.get();
}
const OBJLoader::LoadedMesh* OBJLoader::getMeshInfo(int index) const {
if (index < 0 || index >= static_cast<int>(loadedMeshes.size())) {
return nullptr;
}
return &loadedMeshes[index];
}
// Renderer implementation
Renderer::~Renderer() {
delete shader;
delete texture1;
delete texture2;
delete cubeMesh;
delete sphereMesh;
delete capsuleMesh;
delete skybox;
if (framebuffer) glDeleteFramebuffers(1, &framebuffer);
if (viewportTexture) glDeleteTextures(1, &viewportTexture);
if (rbo) glDeleteRenderbuffers(1, &rbo);
}
void Renderer::initialize() {
shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl");
if (shader->ID == 0) {
std::cerr << "Shader compilation failed!\n";
delete shader;
shader = nullptr;
throw std::runtime_error("Shader error");
}
texture1 = new Texture("Resources/Textures/container.jpg");
texture2 = new Texture("Resources/Textures/awesomeface.png");
cubeMesh = new Mesh(vertices, sizeof(vertices));
auto sphereVerts = generateSphere();
sphereMesh = new Mesh(sphereVerts.data(), sphereVerts.size() * sizeof(float));
auto capsuleVerts = generateCapsule();
capsuleMesh = new Mesh(capsuleVerts.data(), capsuleVerts.size() * sizeof(float));
skybox = new Skybox();
setupFBO();
glEnable(GL_DEPTH_TEST);
}
void Renderer::setupFBO() {
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glGenTextures(1, &viewportTexture);
glBindTexture(GL_TEXTURE_2D, viewportTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentWidth, currentHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, viewportTexture, 0);
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, currentWidth, currentHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Framebuffer setup failed!\n";
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Renderer::resize(int w, int h) {
if (w <= 0 || h <= 0 || (w == currentWidth && h == currentHeight)) return;
currentWidth = w;
currentHeight = h;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glBindTexture(GL_TEXTURE_2D, viewportTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentWidth, currentHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, currentWidth, currentHeight);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Framebuffer incomplete after resize!\n";
}
}
void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj) {
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, currentWidth, currentHeight);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader->use();
shader->setMat4("view", view);
shader->setMat4("projection", proj);
texture1->Bind(GL_TEXTURE0);
texture2->Bind(GL_TEXTURE1);
shader->setInt("texture1", 0);
shader->setInt("texture2", 1);
}
void Renderer::renderSkybox(const glm::mat4& view, const glm::mat4& proj) {
if (skybox) {
glDepthFunc(GL_LEQUAL);
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
glDepthFunc(GL_LESS);
shader->use();
shader->setMat4("view", view);
shader->setMat4("projection", proj);
}
}
void Renderer::renderObject(const SceneObject& obj) {
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, obj.position);
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1, 0, 0));
model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0, 1, 0));
model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0, 0, 1));
model = glm::scale(model, obj.scale);
shader->setMat4("model", model);
switch (obj.type) {
case ObjectType::Cube:
cubeMesh->draw();
break;
case ObjectType::Sphere:
sphereMesh->draw();
break;
case ObjectType::Capsule:
capsuleMesh->draw();
break;
case ObjectType::OBJMesh:
if (obj.meshId >= 0) {
Mesh* objMesh = g_objLoader.getMesh(obj.meshId);
if (objMesh) {
objMesh->draw();
}
}
break;
}
}
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) {
shader->use();
shader->setMat4("view", camera.getViewMatrix());
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
shader->setVec3("lightPos", glm::vec3(4.0f, 6.0f, 4.0f));
shader->setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
shader->setFloat("ambientStrength", 0.25f);
shader->setFloat("specularStrength", 0.8f);
shader->setFloat("shininess", 64.0f);
shader->setFloat("mixAmount", 0.3f);
texture1->Bind(0);
texture2->Bind(1);
for (const auto& obj : sceneObjects) {
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, obj.position);
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
model = glm::scale(model, obj.scale);
shader->setMat4("model", model);
Mesh* meshToDraw = nullptr;
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
else if (obj.type == ObjectType::Sphere) meshToDraw = sphereMesh;
else if (obj.type == ObjectType::Capsule) meshToDraw = capsuleMesh;
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
meshToDraw = g_objLoader.getMesh(obj.meshId);
}
if (meshToDraw) {
meshToDraw->draw();
}
}
if (skybox) {
glm::mat4 view = camera.getViewMatrix();
glm::mat4 proj = glm::perspective(glm::radians(FOV),
(float)currentWidth / currentHeight,
NEAR_PLANE, FAR_PLANE);
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Renderer::endRender() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

86
src/Rendering.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include "Common.h"
#include "SceneObject.h"
#include "../include/Shaders/Shader.h"
#include "../include/Textures/Texture.h"
#include "../include/Skybox/Skybox.h"
// Cube vertex data (position + normal + texcoord)
extern float vertices[];
// Primitive generation functions
std::vector<float> generateSphere(int segments = 32, int rings = 16);
std::vector<float> generateCapsule(int segments = 16, int rings = 8);
class Mesh {
private:
unsigned int VAO, VBO;
int vertexCount;
public:
Mesh(const float* vertexData, size_t dataSizeBytes);
~Mesh();
void draw() const;
int getVertexCount() const { return vertexCount; }
};
class OBJLoader {
public:
struct LoadedMesh {
std::string path;
std::unique_ptr<Mesh> mesh;
std::string name;
int vertexCount = 0;
int faceCount = 0;
bool hasNormals = false;
bool hasTexCoords = false;
};
private:
std::vector<LoadedMesh> loadedMeshes;
public:
int loadOBJ(const std::string& filepath, std::string& errorMsg);
Mesh* getMesh(int index);
const LoadedMesh* getMeshInfo(int index) const;
const std::vector<LoadedMesh>& getAllMeshes() const { return loadedMeshes; }
void clear() { loadedMeshes.clear(); }
size_t getMeshCount() const { return loadedMeshes.size(); }
};
class Camera;
class Renderer {
private:
unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0;
int currentWidth = 800, currentHeight = 600;
Shader* shader = nullptr;
Texture* texture1 = nullptr;
Texture* texture2 = nullptr;
Mesh* cubeMesh = nullptr;
Mesh* sphereMesh = nullptr;
Mesh* capsuleMesh = nullptr;
Skybox* skybox = nullptr;
void setupFBO();
public:
Renderer() = default;
~Renderer();
void initialize();
void resize(int w, int h);
int getWidth() const { return currentWidth; }
int getHeight() const { return currentHeight; }
void beginRender(const glm::mat4& view, const glm::mat4& proj);
void renderSkybox(const glm::mat4& view, const glm::mat4& proj);
void renderObject(const SceneObject& obj);
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects);
void endRender();
Skybox* getSkybox() { return skybox; }
unsigned int getViewportTexture() const { return viewportTexture; }
};

36
src/SceneObject.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include "Common.h"
enum class ObjectType {
Cube,
Sphere,
Capsule,
OBJMesh,
Model // New type for Assimp-loaded models (FBX, GLTF, etc.)
};
enum class ConsoleMessageType {
Info,
Warning,
Error,
Success
};
class SceneObject {
public:
std::string name;
ObjectType type;
glm::vec3 position;
glm::vec3 rotation;
glm::vec3 scale;
int id;
int parentId = -1;
std::vector<int> childIds;
bool isExpanded = true;
std::string meshPath; // Path to OBJ file (for OBJMesh type)
int meshId = -1; // Index into loaded meshes cache
SceneObject(const std::string& name, ObjectType type, int id)
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}
};

View File

@@ -0,0 +1,25 @@
:: This is an example file to generate binaries using Windows Operating System
:: This script is configured to be executed from the source directory
:: Compiled binaries will be placed in BINARIES_DIR\code\CONFIG
:: NOTE
:: The build process will generate a config.h file that is placed in BINARIES_DIR\include
:: This file must be merged with SOURCE_DIR\include
:: You should write yourself a script that copies the files where you want them.
:: Also see: https://github.com/assimp/assimp/pull/2646
SET SOURCE_DIR=.
SET GENERATOR=Visual Studio 16 2019
SET BINARIES_DIR="./build/Win32"
cmake . -G "%GENERATOR%" -A Win32 -S %SOURCE_DIR% -B %BINARIES_DIR%
cmake --build %BINARIES_DIR% --config debug
cmake --build %BINARIES_DIR% --config release
SET BINARIES_DIR="./build/x64"
cmake . -G "%GENERATOR%" -A x64 -S %SOURCE_DIR% -B %BINARIES_DIR%
cmake --build %BINARIES_DIR% --config debug
cmake --build %BINARIES_DIR% --config release
PAUSE

114
src/ThirdParty/assimp/Build.md vendored Normal file
View File

@@ -0,0 +1,114 @@
# Build / Install Instructions
## Manual build instructions
### Install prerequisites
You need to install
* cmake
* Your compiler (must support C++17 and C99 at least)
* For Windows
* DX-SDK 9 if you want to use our 3D-Viewer
### Get the source
Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
```bash
git clone https://github.com/assimp/assimp.git
```
### Build from source:
* For *assimp.lib* without any tools:
```bash
cd assimp
cmake CMakeLists.txt
cmake --build .
```
* For assimp with the common tools like *assimp-cmd*
```bash
cd assimp
cmake CMakeLists.txt -DASSIMP_BUILD_ASSIMP_TOOLS=ON
cmake --build .
```
Note that by default this builds a shared library into the `bin` directory. If you want to build it as a static library see the build options at the bottom of this file.
### Build instructions for Windows with Visual-Studio
First, you have to install Visual-Studio on your windows-system. You can get the Community-Version for free [here](https://visualstudio.microsoft.com/downloads/)
To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
```bash
cmake CMakeLists.txt
```
This will generate the project files for the visual studio. All dependencies used to build Asset-Importer-Lib shall be part of the repo. If you want to use you own zlib installation this is possible as well. Check the options for it.
### Build instructions for Windows with UWP
See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
### Build instructions for MinGW
Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag
required to compile some of assimp's files, especially for debug builds.
Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work.
Please see [CMake Cross Compiling](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling) for general information on CMake Toolchains.
Some users have had success building assimp using MinGW on Linux using [polly](https://github.com/ruslo/polly/).
The following toolchain, which is not maintained by assimp, seems to work on Linux: [linux-mingw-w64-gnuxx11.cmake](https://github.com/ruslo/polly/blob/master/linux-mingw-w64-gnuxx11.cmake)
The following toolchain may or may not be helpful for building assimp using MinGW on Windows (untested):
[mingw-cxx17.cmake](https://github.com/ruslo/polly/blob/master/mingw-cxx17.cmake)
Besides the toolchain, compilation should be the same as for Linux / Unix.
### CMake build options
The cmake-build-environment provides options to configure the build. The following options can be used:
- **ASSIMP_HUNTER_ENABLED (default OFF)**: Enable Hunter package manager support.
- **BUILD_SHARED_LIBS (default ON)**: Generation of shared libs (dll for windows, so for Linux). Set this to OFF to get a static lib.
- **ASSIMP_BUILD_FRAMEWORK (default OFF, MacOnly)**: Build package as Mac OS X Framework bundle.
- **ASSIMP_DOUBLE_PRECISION (default OFF)**: All data will be stored as double values.
- **ASSIMP_OPT_BUILD_PACKAGES (default OFF)**: Set to ON to generate CPack configuration files and packaging targets.
- **ASSIMP_ANDROID_JNIIOSYSTEM (default OFF)**: Android JNI IOSystem support is active.
- **ASSIMP_NO_EXPORT (default OFF)**: Disable Assimp's export functionality.
- **ASSIMP_BUILD_ZLIB (default OFF)**: Build our own zlib.
- **ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT (default ON)**: Build Assimp with all exporters enabled.
- **ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT (default ON)**: Build Assimp with (most) importers enabled. Currently, USD importer is not included. See ASSIMP_BUILD_USD_IMPORTER.
- **ASSIMP_BUILD_ASSIMP_TOOLS (default OFF)**: If the supplementary tools for Assimp are built in addition to the library.
- **ASSIMP_BUILD_SAMPLES (default OFF)**: If the official samples are built as well (needs Glut).
- **ASSIMP_BUILD_TESTS (default ON)**: If the test suite for Assimp is built in addition to the library.
- **ASSIMP_COVERALLS (default OFF)**: Enable this to measure test coverage.
- **ASSIMP_INSTALL (default ON)**: Install Assimp library. Disable this if you want to use Assimp as a submodule.
- **ASSIMP_WARNINGS_AS_ERRORS (default ON)**: Treat all warnings as errors.
- **ASSIMP_ASAN (default OFF)**: Enable AddressSanitizer.
- **ASSIMP_UBSAN (default OFF)**: Enable Undefined Behavior sanitizer.
- **ASSIMP_BUILD_DOCS (default OFF)**: Build documentation using Doxygen. OBSOLETE, see https://github.com/assimp/assimp-docs
- **ASSIMP_INJECT_DEBUG_POSTFIX (default ON)**: Inject debug postfix in .a/.so/.lib/.dll lib names
- **ASSIMP_IGNORE_GIT_HASH (default OFF)**: Don't call git to get the hash.
- **ASSIMP_INSTALL_PDB (default ON)**: Install MSVC debug files.
- **USE_STATIC_CRT (default OFF)**: Link against the static MSVC runtime libraries.
- **ASSIMP_BUILD_DRACO (default OFF)**: Build Draco libraries. Primarily for glTF.
- **ASSIMP_BUILD_ASSIMP_VIEW (default ON, if DirectX found, OFF otherwise)**: Build Assimp view tool (requires DirectX).
- **ASSIMP_BUILD_USD_IMPORTER (default OFF, requires ASSIMP_WARNINGS_AS_ERRORS to be OFF)**: Build USD importer, defaults to off for CI purposes
### Install prebuild binaries
## Install on all platforms using vcpkg
You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
```bash
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install assimp
```
The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
### Install on Ubuntu
You can install the Asset-Importer-Lib via apt:
```
sudo apt-get update
sudo apt-get install libassimp-dev
```
### Install pyassimp
You need to have pip installed:
```
pip install pyassimp
```
### Get the SDK from itch.io
Just check [itch.io](https://kimkulling.itch.io/the-asset-importer-lib)

945
src/ThirdParty/assimp/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,945 @@
# Open Asset Import Library (assimp)
# ----------------------------------------------------------------------
# Copyright (c) 2006-2025, assimp team
#
# All rights reserved.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the
# following conditions are met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the
# following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the
# following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# * Neither the name of the assimp team, nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior
# written permission of the assimp team.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#----------------------------------------------------------------------
SET(CMAKE_POLICY_DEFAULT_CMP0012 NEW)
SET(CMAKE_POLICY_DEFAULT_CMP0074 NEW)
SET(CMAKE_POLICY_DEFAULT_CMP0092 NEW)
CMAKE_MINIMUM_REQUIRED( VERSION 3.22 )
#================================================================================#
# Model formats not enabled by default
#
# 3rd party projects may not adhere to strict standards enforced by assimp,
# in which case those formats must be opt-in; otherwise the 3rd party code
# would fail assimp CI checks
#================================================================================#
# M3D format import support (assimp integration no longer supported by M3D format author)
# User may override these in their CMake script to provide M3D import/export support
# (M3D importer/exporter was disabled for assimp release 5.1 or later)
OPTION(ASSIMP_BUILD_M3D_IMPORTER "Enable M3D file import" off)
OPTION(ASSIMP_BUILD_M3D_EXPORTER "Enable M3D file export" off)
# Experimental USD importer: disabled, need to opt-in
# Note: assimp github PR automatic checks will fail the PR due to compiler warnings in
# the external, 3rd party tinyusdz code which isn't technically part of the PR since it's
# auto-cloned during build; so MUST disable the feature or the PR will be rejected
OPTION(ASSIMP_BUILD_USD_IMPORTER "Enable USD file import" off)
OPTION(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" off)
# VRML (.wrl/.x3dv) file import support by leveraging X3D importer and 3rd party file
# format converter to convert .wrl/.x3dv files to X3D-compatible .xml
# (Need to make this opt-in because 3rd party code triggers lots of CI code quality warnings)
OPTION(ASSIMP_BUILD_VRML_IMPORTER "Enable VRML (.wrl/.x3dv) file import" off)
#--------------------------------------------------------------------------------#
# Internal impl for optional model formats
#--------------------------------------------------------------------------------#
# Internal/private M3D logic
if (NOT ASSIMP_BUILD_M3D_IMPORTER)
ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER)
endif () # if (not ASSIMP_BUILD_M3D_IMPORTER)
if (NOT ASSIMP_BUILD_M3D_EXPORTER)
ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER)
endif () # if (not ASSIMP_BUILD_M3D_EXPORTER)
# Internal/private VRML logic
if (NOT ASSIMP_BUILD_VRML_IMPORTER)
ADD_DEFINITIONS( -DASSIMP_BUILD_NO_VRML_IMPORTER)
endif () # if (not ASSIMP_BUILD_VRML_IMPORTER)
#================================================================================#
option(ASSIMP_BUILD_USE_CCACHE "Use ccache to speed up compilation." on)
IF(ASSIMP_BUILD_USE_CCACHE)
find_program(CCACHE_PATH ccache)
IF (CCACHE_PATH)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PATH})
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PATH})
ENDIF()
ENDIF()
# Toggles the use of the hunter package manager
option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
IF(ASSIMP_HUNTER_ENABLED)
include("cmake-modules/HunterGate.cmake")
HunterGate(
URL "https://github.com/cpp-pm/hunter/archive/v0.25.8.tar.gz"
SHA1 "26c79d587883ec910bce168e25f6ac4595f97033"
)
ADD_DEFINITIONS(-DASSIMP_USE_HUNTER)
ENDIF()
PROJECT(Assimp VERSION 6.0.2
LANGUAGES C CXX
DESCRIPTION "Open Asset Import Library (Assimp) is a library to import various well-known 3D model formats in a uniform manner."
)
# All supported options ###############################################
OPTION( BUILD_SHARED_LIBS
"Build package with shared libraries."
ON
)
OPTION( ASSIMP_BUILD_FRAMEWORK
"Build package as Mac OS X Framework bundle."
OFF
)
OPTION( ASSIMP_DOUBLE_PRECISION
"Set to ON to enable double precision processing"
OFF
)
OPTION( ASSIMP_OPT_BUILD_PACKAGES
"Set to ON to generate CPack configuration files and packaging targets"
OFF
)
OPTION( ASSIMP_ANDROID_JNIIOSYSTEM
"Android JNI IOSystem support is active"
OFF
)
OPTION( ASSIMP_NO_EXPORT
"Disable Assimp's export functionality."
OFF
)
OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
"If the supplementary tools for Assimp are built in addition to the library."
OFF
)
OPTION ( ASSIMP_BUILD_SAMPLES
"If the official samples are built as well (needs Glut)."
OFF
)
OPTION ( ASSIMP_BUILD_TESTS
"If the test suite for Assimp is built in addition to the library."
ON
)
OPTION ( ASSIMP_COVERALLS
"Enable this to measure test coverage."
OFF
)
OPTION( ASSIMP_INSTALL
"Disable this if you want to use assimp as a submodule."
ON
)
OPTION ( ASSIMP_WARNINGS_AS_ERRORS
"Treat all warnings as errors."
ON
)
OPTION ( ASSIMP_ASAN
"Enable AddressSanitizer."
OFF
)
OPTION ( ASSIMP_UBSAN
"Enable Undefined Behavior sanitizer."
OFF
)
OPTION ( ASSIMP_BUILD_DOCS
"Build documentation using Doxygen."
OFF
)
OPTION( ASSIMP_INJECT_DEBUG_POSTFIX
"Inject debug postfix in .a/.so/.dll lib names"
ON
)
OPTION ( ASSIMP_IGNORE_GIT_HASH
"Don't call git to get the hash."
OFF
)
IF (WIN32)
OPTION( ASSIMP_BUILD_ZLIB
"Build your zlib"
ON
)
ELSE()
OPTION( ASSIMP_BUILD_ZLIB
"Build your zlib"
OFF
)
ENDIF()
IF (WIN32)
# Use a subset of Windows.h
ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN )
IF(MSVC)
OPTION( ASSIMP_INSTALL_PDB
"Create MSVC debug symbol files and add to Install target."
ON )
IF(NOT (MSVC_VERSION LESS 1900))
# Multibyte character set has been deprecated since at least MSVC2015 (possibly earlier)
ADD_DEFINITIONS( -DUNICODE -D_UNICODE )
ENDIF()
# Link statically against c/c++ lib to avoid missing redistributable such as
# "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users
# a choice to opt for the shared runtime if they want.
option(USE_STATIC_CRT "Link against the static runtime libraries." OFF)
# The CMAKE_CXX_FLAGS vars can be overridden by some Visual Studio generators, so we use an alternative
# global method here:
if (${USE_STATIC_CRT})
add_compile_options(
$<$<CONFIG:>:/MT>
$<$<CONFIG:Debug>:/MTd>
$<$<CONFIG:Release>:/MT>
)
endif()
ENDIF()
ENDIF()
IF (IOS AND NOT ASSIMP_HUNTER_ENABLED)
IF (NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF ()
ADD_DEFINITIONS(-DENABLE_BITCODE)
ENDIF ()
IF (ASSIMP_BUILD_FRAMEWORK)
SET (BUILD_SHARED_LIBS ON)
MESSAGE(STATUS "Framework bundle building enabled")
ENDIF()
IF(NOT BUILD_SHARED_LIBS)
MESSAGE(STATUS "Shared libraries disabled")
SET(LINK_SEARCH_START_STATIC TRUE)
SET(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ${CMAKE_FIND_LIBRARY_SUFFIXES})
ELSE()
MESSAGE(STATUS "Shared libraries enabled")
ENDIF()
# Define here the needed parameters
SET (ASSIMP_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
SET (ASSIMP_VERSION_MINOR ${PROJECT_VERSION_MINOR})
SET (ASSIMP_VERSION_PATCH ${PROJECT_VERSION_PATCH})
SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH})
SET (ASSIMP_SOVERSION 6)
SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)
IF(NOT ASSIMP_IGNORE_GIT_HASH)
# Get the current working branch
EXECUTE_PROCESS(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
# Get the latest abbreviated commit hash of the working branch
EXECUTE_PROCESS(
COMMAND git rev-parse --short=8 HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
ENDIF()
IF(NOT GIT_COMMIT_HASH)
SET(GIT_COMMIT_HASH 0)
ENDIF()
IF(ASSIMP_DOUBLE_PRECISION)
ADD_DEFINITIONS(-DASSIMP_DOUBLE_PRECISION)
ENDIF()
INCLUDE_DIRECTORIES( BEFORE
./
code/
include
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/include
)
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules" )
SET(LIBASSIMP_COMPONENT "libassimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH}" )
SET(LIBASSIMP-DEV_COMPONENT "libassimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH}-dev" )
SET(CPACK_COMPONENTS_ALL assimp-bin ${LIBASSIMP_COMPONENT} ${LIBASSIMP-DEV_COMPONENT} assimp-dev)
SET(ASSIMP_LIBRARY_SUFFIX "" CACHE STRING "Suffix to append to library names")
IF( UNIX )
# Use GNUInstallDirs for Unix predefined directories
INCLUDE(GNUInstallDirs)
# Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit Linux
IF(NOT ${OPERATING_SYSTEM} MATCHES "Android")
IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit Linux
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
ENDIF()
ENDIF()
ENDIF()
IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND WIN32)
ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
ENDIF()
IF( MSVC OR "${CMAKE_CXX_SIMULATE_ID}" MATCHES "MSVC") # clang with MSVC ABI
ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
endif ()
# Grouped compiler settings ########################################
IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU)
IF(NOT ASSIMP_HUNTER_ENABLED)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
ENDIF()
IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU")
MESSAGE(STATUS "GCC13 detected disabling \"-Wdangling-reference\" in Cpp files as it appears to be a false positive")
ADD_COMPILE_OPTIONS("$<$<COMPILE_LANGUAGE:CXX>:-Wno-dangling-reference>")
ENDIF()
# hide all not-exported symbols
IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "mips64" )
SET(CMAKE_CXX_FLAGS "-mxgot -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}")
SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
SET(LIBSTDC++_LIBRARIES -lstdc++)
ELSE()
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}")
SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
SET(LIBSTDC++_LIBRARIES -lstdc++)
ENDIF()
ELSEIF(MSVC)
# enable multi-core compilation with MSVC
IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl
ADD_COMPILE_OPTIONS(/bigobj)
ELSE() # msvc
ADD_COMPILE_OPTIONS(/MP /bigobj)
ENDIF()
# disable "elements of array '' will be default initialized" warning on MSVC2013
IF(MSVC12)
ADD_COMPILE_OPTIONS(/wd4351)
ENDIF()
# supress warning for double to float conversion if Double precision is activated
ADD_COMPILE_OPTIONS(/wd4244)
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
# Allow user to disable PDBs
if(ASSIMP_INSTALL_PDB)
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
elseif((GENERATOR_IS_MULTI_CONFIG) OR (CMAKE_BUILD_TYPE MATCHES Release))
message("-- MSVC PDB generation disabled. Release binary will not be debuggable.")
endif()
if(NOT /utf-8 IN_LIST CMAKE_CXX_FLAGS)
# Source code is encoded in UTF-8
ADD_COMPILE_OPTIONS(/source-charset:utf-8)
endif()
ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
IF(NOT ASSIMP_HUNTER_ENABLED)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
ENDIF()
SET(CMAKE_CXX_FLAGS "-Wno-deprecated-non-prototype -fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" )
SET(CMAKE_C_FLAGS "-Wno-deprecated-non-prototype -fno-strict-aliasing ${CMAKE_C_FLAGS}")
ELSEIF( MINGW )
IF (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
message(FATAL_ERROR "MinGW is too old to be supported. Please update MinGW and try again.")
ELSEIF(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.3)
message(WARNING "MinGW is old, if you experience errors, update MinGW.")
ENDIF()
IF(NOT ASSIMP_HUNTER_ENABLED)
SET(CMAKE_CXX_FLAGS "-std=gnu++17 ${CMAKE_CXX_FLAGS}")
SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
ENDIF()
IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wno-dangling-reference -Wall -Wno-long-long -Wa,-mbig-obj -g ${CMAKE_CXX_FLAGS}")
ELSE()
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wno-dangling-reference -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}")
ENDIF()
SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
ENDIF()
IF ( IOS AND NOT ASSIMP_HUNTER_ENABLED)
IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
ELSE()
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
ENDIF()
ENDIF()
IF (ASSIMP_COVERALLS)
MESSAGE(STATUS "Coveralls enabled")
INCLUDE(Coveralls)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
ENDIF()
IF (ASSIMP_ASAN)
MESSAGE(STATUS "AddressSanitizer enabled")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
ENDIF()
IF (ASSIMP_UBSAN)
MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
ENDIF()
INCLUDE (FindPkgMacros)
INCLUDE (PrecompiledHeader)
# Set Assimp project output directory variables.
# Will respect top-level CMAKE_*_OUTPUT_DIRECTORY variables if any are set.
IF(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
SET(ASSIMP_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" CACHE STRING "Path for runtime output files")
ELSE()
SET(ASSIMP_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} CACHE STRING "Path for runtime output files")
ENDIF()
IF(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
SET(ASSIMP_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" CACHE STRING "Path for library output files")
ELSE()
SET(ASSIMP_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} CACHE STRING "Path for runtime output files")
ENDIF()
IF(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
SET(ASSIMP_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib" CACHE STRING "Path for library output files")
ELSE()
SET(ASSIMP_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} CACHE STRING "Path for runtime output files")
ENDIF()
# Macro used to set the output directories of a target to the
# respective Assimp output directories.
MACRO(TARGET_USE_COMMON_OUTPUT_DIRECTORY target)
set_target_properties(${target} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${ASSIMP_RUNTIME_OUTPUT_DIRECTORY}
LIBRARY_OUTPUT_DIRECTORY ${ASSIMP_LIBRARY_OUTPUT_DIRECTORY}
ARCHIVE_OUTPUT_DIRECTORY ${ASSIMP_ARCHIVE_OUTPUT_DIRECTORY}
)
ENDMACRO()
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
IF (ASSIMP_INJECT_DEBUG_POSTFIX AND (is_multi_config OR CMAKE_BUILD_TYPE STREQUAL "Debug"))
SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools")
ELSE()
SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
ENDIF()
# Only generate this target if no higher-level project already has
IF (NOT TARGET uninstall AND ASSIMP_INSTALL)
# add make uninstall capability
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
ENDIF()
IF( UNIX )
# Use GNUInstallDirs for Unix predefined directories
INCLUDE(GNUInstallDirs)
SET( ASSIMP_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR})
SET( ASSIMP_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
SET( ASSIMP_BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR})
ELSE()
# Cache these to allow the user to override them on non-Unix platforms
SET( ASSIMP_LIB_INSTALL_DIR "lib" CACHE STRING
"Path the built library files are installed to." )
SET( ASSIMP_INCLUDE_INSTALL_DIR "include" CACHE STRING
"Path the header files are installed to." )
SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING
"Path the tool executables are installed to." )
SET(CMAKE_INSTALL_FULL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/${ASSIMP_INCLUDE_INSTALL_DIR})
SET(CMAKE_INSTALL_FULL_LIBDIR ${CMAKE_INSTALL_PREFIX}/${ASSIMP_LIB_INSTALL_DIR})
SET(CMAKE_INSTALL_FULL_BINDIR ${CMAKE_INSTALL_PREFIX}/${ASSIMP_BIN_INSTALL_DIR})
ENDIF()
set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
IF(ASSIMP_HUNTER_ENABLED)
SET(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}")
SET(CMAKE_CONFIG_TEMPLATE_FILE "cmake-modules/assimp-hunter-config.cmake.in")
SET(NAMESPACE "${PROJECT_NAME}::")
SET(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
SET(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
SET(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake")
ELSE()
SET(CONFIG_INSTALL_DIR "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}")
SET(CMAKE_CONFIG_TEMPLATE_FILE "cmake-modules/assimp-plain-config.cmake.in")
string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWERCASE)
SET(NAMESPACE "${PROJECT_NAME_LOWERCASE}::")
SET(TARGETS_EXPORT_NAME "${PROJECT_NAME_LOWERCASE}Targets")
SET(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME_LOWERCASE}ConfigVersion.cmake")
SET(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME_LOWERCASE}Config.cmake")
ENDIF()
set(INCLUDE_INSTALL_DIR "include")
# Include module with fuction 'write_basic_package_version_file'
include(CMakePackageConfigHelpers)
# Note: PROJECT_VERSION is used as a VERSION
write_basic_package_version_file("${VERSION_CONFIG}" COMPATIBILITY SameMajorVersion)
configure_package_config_file(
${CMAKE_CONFIG_TEMPLATE_FILE}
"${PROJECT_CONFIG}"
INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}"
)
IF(ASSIMP_INSTALL)
INSTALL(
FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}"
DESTINATION "${CONFIG_INSTALL_DIR}"
COMPONENT ${LIBASSIMP-DEV_COMPONENT}
)
INSTALL(
EXPORT "${TARGETS_EXPORT_NAME}"
NAMESPACE "${NAMESPACE}"
DESTINATION "${CONFIG_INSTALL_DIR}"
COMPONENT ${LIBASSIMP-DEV_COMPONENT}
)
ENDIF()
IF( ASSIMP_BUILD_DOCS )
ADD_SUBDIRECTORY(doc)
ENDIF()
# Search for external dependencies, and build them from source if not found
# Search for zlib
IF(ASSIMP_HUNTER_ENABLED)
hunter_add_package(ZLIB)
find_package(ZLIB CONFIG REQUIRED)
add_definitions(-DASSIMP_BUILD_NO_OWN_ZLIB)
SET(ZLIB_FOUND TRUE)
SET(ZLIB_LIBRARIES ZLIB::zlib)
SET(ASSIMP_BUILD_MINIZIP TRUE)
ELSE()
# If the zlib is already found outside, add an export in case assimpTargets can't find it.
IF( ZLIB_FOUND AND ASSIMP_INSTALL)
INSTALL( TARGETS zlib zlibstatic
EXPORT "${TARGETS_EXPORT_NAME}")
ENDIF()
IF ( NOT ASSIMP_BUILD_ZLIB )
FIND_PACKAGE(ZLIB)
ENDIF()
IF ( NOT ZLIB_FOUND AND NOT ASSIMP_BUILD_ZLIB )
message( FATAL_ERROR
"Build configured with -DASSIMP_BUILD_ZLIB=OFF but unable to find zlib"
)
ELSEIF( NOT ZLIB_FOUND )
MESSAGE(STATUS "compiling zlib from sources")
INCLUDE(CheckIncludeFile)
INCLUDE(CheckTypeSize)
INCLUDE(CheckFunctionExists)
# Explicitly turn off ASM686 and AMD64 cmake options.
# The AMD64 option causes a build failure on MSVC and the ASM builds seem to have problems:
# https://github.com/madler/zlib/issues/41#issuecomment-125848075
# Also prevents these options from "polluting" the cmake options if assimp is being
# included as a submodule.
SET(ASM686 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE )
SET(AMD64 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE )
# compile from sources
ADD_SUBDIRECTORY(contrib/zlib)
SET(ZLIB_FOUND 1)
SET(ZLIB_LIBRARIES zlibstatic)
SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib)
# need to ensure we don't link with system zlib or minizip as well.
SET(ASSIMP_BUILD_MINIZIP 1)
ELSE()
ADD_DEFINITIONS(-DASSIMP_BUILD_NO_OWN_ZLIB)
SET(ZLIB_LIBRARIES_LINKED -l${ZLIB_LIBRARIES})
ENDIF()
INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR})
ENDIF()
IF( NOT IOS )
IF( NOT ASSIMP_BUILD_MINIZIP )
use_pkgconfig(UNZIP minizip)
ENDIF()
ELSE ()
IF( NOT BUILD_SHARED_LIBS )
IF( NOT ASSIMP_BUILD_MINIZIP )
USE_PKGCONFIG(UNZIP minizip)
ENDIF()
ENDIF ()
ENDIF ()
IF ( ASSIMP_NO_EXPORT )
ADD_DEFINITIONS( -DASSIMP_BUILD_NO_EXPORT)
MESSAGE( STATUS "Build an import-only version of Assimp." )
ENDIF()
SET ( ASSIMP_BUILD_ARCHITECTURE "" CACHE STRING
"describe the current architecture."
)
IF( ASSIMP_BUILD_ARCHITECTURE STREQUAL "")
ELSE()
ADD_DEFINITIONS ( -D'ASSIMP_BUILD_ARCHITECTURE="${ASSIMP_BUILD_ARCHITECTURE}"' )
ENDIF()
# ${CMAKE_GENERATOR}
SET ( ASSIMP_BUILD_COMPILER "" CACHE STRING
"describe the current compiler."
)
IF( ASSIMP_BUILD_COMPILER STREQUAL "")
ELSE()
ADD_DEFINITIONS ( -D'ASSIMP_BUILD_COMPILER="${ASSIMP_BUILD_COMPILER}"' )
ENDIF()
MARK_AS_ADVANCED ( ASSIMP_BUILD_ARCHITECTURE ASSIMP_BUILD_COMPILER )
SET ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER OFF CACHE BOOL
"Build the C4D importer, which relies on the non-free Cineware SDK."
)
IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/includes")
IF (WIN32)
# pick the correct prebuilt library
IF(MSVC143)
SET(C4D_LIB_POSTFIX "_2022")
ELSEIF(MSV142)
SET(C4D_LIB_POSTFIX "_2019")
ELSEIF(MSVC15)
SET(C4D_LIB_POSTFIX "_2017")
ELSEIF(MSVC14)
SET(C4D_LIB_POSTFIX "_2015")
ELSEIF(MSVC12)
SET(C4D_LIB_POSTFIX "_2013")
ELSEIF(MSVC11)
SET(C4D_LIB_POSTFIX "_2012")
ELSEIF(MSVC10)
SET(C4D_LIB_POSTFIX "_2010")
ELSE()
MESSAGE( FATAL_ERROR
"C4D for Windows is currently only supported with MSVC 10, 11, 12, 14, 14.2, 14.3"
)
ENDIF()
SET(C4D_LIB_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/libraries/win")
SET(C4D_DEBUG_LIBRARIES
"${C4D_LIB_BASE_PATH}/cinewarelib${C4D_LIB_POSTFIX}/cinewarelib_debug.lib"
"${C4D_LIB_BASE_PATH}/jpeglib${C4D_LIB_POSTFIX}/jpeglib_debug.lib"
)
SET(C4D_RELEASE_LIBRARIES
"${C4D_LIB_BASE_PATH}/cinewarelib${C4D_LIB_POSTFIX}/cinewarelib_release.lib"
"${C4D_LIB_BASE_PATH}/jpeglib${C4D_LIB_POSTFIX}/jpeglib_release.lib"
)
# winsock and winmm are necessary (and undocumented) dependencies of Cineware SDK because
# it can be used to communicate with a running Cinema 4D instance
SET(C4D_EXTRA_LIBRARIES WSock32.lib Winmm.lib)
ELSEIF (APPLE)
SET(C4D_LIB_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/libraries/osx")
SET(C4D_DEBUG_LIBRARIES
"${C4D_LIB_BASE_PATH}/debug/libcinewarelib.a"
"${C4D_LIB_BASE_PATH}/debug/libjpeglib.a"
)
SET(C4D_RELEASE_LIBRARIES
"${C4D_LIB_BASE_PATH}/release/libcinewarelib.a"
"${C4D_LIB_BASE_PATH}/release/libjpeglib.a"
)
ELSE()
MESSAGE( FATAL_ERROR
"C4D is currently only available on Windows and macOS with Cineware SDK installed in contrib/Cineware"
)
ENDIF()
ELSE ()
ADD_DEFINITIONS( -DASSIMP_BUILD_NO_C4D_IMPORTER )
ENDIF ()
IF(ASSIMP_BUILD_DRACO_STATIC)
SET(ASSIMP_BUILD_DRACO ON)
ENDIF()
# Draco requires cmake 3.12
IF (DEFINED CMAKE_VERSION AND "${CMAKE_VERSION}" VERSION_LESS "3.12")
MESSAGE(NOTICE "draco requires cmake 3.12 or newer, cmake is ${CMAKE_VERSION} . Draco is disabled")
SET ( ASSIMP_BUILD_DRACO OFF CACHE BOOL "Disabled: Draco requires newer cmake" FORCE )
ELSE()
OPTION ( ASSIMP_BUILD_DRACO "If the Draco libraries are to be built. Primarily for glTF" OFF )
IF ( ASSIMP_BUILD_DRACO )
# Primarily for glTF v2
# Enable Draco glTF feature set
SET(DRACO_GLTF_BITSTREAM ON CACHE BOOL "" FORCE)
# Disable unnecessary or omitted components
SET(DRACO_JS_GLUE OFF CACHE BOOL "" FORCE)
SET(DRACO_WASM OFF CACHE BOOL "" FORCE)
SET(DRACO_MAYA_PLUGIN OFF CACHE BOOL "" FORCE)
SET(DRACO_UNITY_PLUGIN OFF CACHE BOOL "" FORCE)
SET(DRACO_TESTS OFF CACHE BOOL "" FORCE)
IF(ASSIMP_HUNTER_ENABLED)
hunter_add_package(draco)
find_package(draco CONFIG REQUIRED)
SET(draco_LIBRARIES draco::draco)
ELSE()
# Draco 1.4.1 has many warnings and will not build with /WX or -Werror
# See https://github.com/google/draco/issues/672
# and https://github.com/google/draco/issues/673
IF(MSVC)
SET(DRACO_CXX_FLAGS "/W0")
ELSE()
LIST(APPEND DRACO_CXX_FLAGS
"-Wno-bool-compare"
"-Wno-comment"
"-Wno-maybe-uninitialized"
"-Wno-sign-compare"
"-Wno-unused-local-typedefs"
)
IF(NOT ASSIMP_BUILD_DRACO_STATIC)
# Draco 1.4.1 does not explicitly export any symbols under GCC/clang
LIST(APPEND DRACO_CXX_FLAGS
"-fvisibility=default"
)
ENDIF()
ENDIF()
# Don't build or install all of Draco by default
ADD_SUBDIRECTORY( "contrib/draco" EXCLUDE_FROM_ALL )
IF(ASSIMP_BUILD_DRACO_STATIC)
set_property(DIRECTORY "contrib/draco" PROPERTY BUILD_SHARED_LIBS OFF)
ENDIF()
IF(MSVC OR WIN32)
SET(draco_LIBRARIES "draco")
ELSE()
IF(ASSIMP_BUILD_DRACO_STATIC)
SET(draco_LIBRARIES "draco_static")
ELSE()
SET(draco_LIBRARIES "draco_shared")
ENDIF()
ENDIF()
# Don't build the draco command-line tools by default
set_target_properties(draco_encoder draco_decoder PROPERTIES
EXCLUDE_FROM_ALL TRUE
EXCLUDE_FROM_DEFAULT_BUILD TRUE
)
# Do build the draco shared library
set_target_properties(${draco_LIBRARIES} PROPERTIES
EXCLUDE_FROM_ALL FALSE
EXCLUDE_FROM_DEFAULT_BUILD FALSE
)
TARGET_USE_COMMON_OUTPUT_DIRECTORY(${draco_LIBRARIES})
TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_encoder)
TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_decoder)
SET(draco_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/contrib/draco/src")
# This is probably wrong
IF (ASSIMP_INSTALL)
INSTALL( TARGETS ${draco_LIBRARIES}
EXPORT "${TARGETS_EXPORT_NAME}"
LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR}
FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
COMPONENT ${LIBASSIMP_COMPONENT}
INCLUDES DESTINATION include
)
ENDIF()
ENDIF()
ENDIF()
ENDIF()
# Main assimp code
ADD_SUBDIRECTORY( code/ )
IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
# The viewer for windows only
IF (WIN32)
FIND_PACKAGE(DirectX)
OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
IF ( ASSIMP_BUILD_ASSIMP_VIEW )
ADD_SUBDIRECTORY( tools/assimp_view/ )
ENDIF ()
ELSE()
MESSAGE("Building Assimp Viewer only supported on Windows.")
ENDIF ()
# The command line tool
ADD_SUBDIRECTORY( tools/assimp_cmd/ )
ENDIF ()
IF ( ASSIMP_BUILD_SAMPLES )
SET( SAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/samples )
SET( SAMPLES_SHARED_CODE_DIR ${SAMPLES_DIR}/SharedCode )
IF ( WIN32 )
ADD_SUBDIRECTORY( samples/SimpleTexturedOpenGL/ )
ADD_SUBDIRECTORY( samples/SimpleTexturedDirectx11 )
ENDIF ()
ADD_SUBDIRECTORY( samples/SimpleOpenGL/ )
ENDIF ()
IF ( ASSIMP_BUILD_TESTS )
ADD_SUBDIRECTORY( test/ )
ENDIF ()
# Generate a pkg-config .pc, revision.h, and config.h for the Assimp library.
CONFIGURE_FILE( "${PROJECT_SOURCE_DIR}/assimp.pc.in" "${PROJECT_BINARY_DIR}/assimp.pc" @ONLY )
IF ( ASSIMP_INSTALL )
INSTALL( FILES "${PROJECT_BINARY_DIR}/assimp.pc" DESTINATION ${ASSIMP_LIB_INSTALL_DIR}/pkgconfig/ COMPONENT ${LIBASSIMP-DEV_COMPONENT})
ENDIF()
CONFIGURE_FILE(
${CMAKE_CURRENT_LIST_DIR}/include/assimp/revision.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/assimp/revision.h
)
CONFIGURE_FILE(
${CMAKE_CURRENT_LIST_DIR}/include/assimp/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/assimp/config.h
)
IF ( ASSIMP_INSTALL )
IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES)
# Packing information
SET(CPACK_PACKAGE_NAME "assimp{ASSIMP_VERSION_MAJOR}.{ASSIMP_VERSION_MINOR}")
SET(CPACK_PACKAGE_CONTACT "" CACHE STRING "Package maintainer and PGP signer.")
SET(CPACK_PACKAGE_VENDOR "https://github.com/assimp")
SET(CPACK_PACKAGE_DISPLAY_NAME "Assimp ${ASSIMP_VERSION}")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY " - Open Asset Import Library ${ASSIMP_VERSION}")
SET(CPACK_PACKAGE_VERSION "${ASSIMP_VERSION}.${ASSIMP_PACKAGE_VERSION}" )
SET(CPACK_PACKAGE_VERSION_MAJOR "${ASSIMP_VERSION_MAJOR}")
SET(CPACK_PACKAGE_VERSION_MINOR "${ASSIMP_VERSION_MINOR}")
SET(CPACK_PACKAGE_VERSION_PATCH "${ASSIMP_VERSION_PATCH}")
SET(CPACK_PACKAGE_INSTALL_DIRECTORY "assimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
STRING(TOUPPER ${LIBASSIMP_COMPONENT} "LIBASSIMP_COMPONENT_UPPER")
STRING(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER")
SET(CPACK_COMPONENT_ASSIMP-BIN_DISPLAY_NAME "tools")
SET(CPACK_COMPONENT_ASSIMP-BIN_DEPENDS "${LIBASSIMP_COMPONENT}" )
SET(CPACK_COMPONENT_${LIBASSIMP_COMPONENT_UPPER}_DISPLAY_NAME "libraries")
SET(CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT_UPPER}_DISPLAY_NAME "common headers and installs")
SET(CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT_UPPER}_DEPENDS $ "{LIBASSIMP_COMPONENT}" )
SET(CPACK_COMPONENT_ASSIMP-DEV_DISPLAY_NAME "${CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT}_DISPLAY_NAME}" )
SET(CPACK_COMPONENT_ASSIMP-DEV_DEPENDS "${LIBASSIMP-DEV_COMPONENT}" )
SET(CPACK_DEBIAN_BUILD_DEPENDS debhelper cmake zlib1g-dev pkg-config)
# debian
SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
SET(CPACK_DEBIAN_CMAKE_OPTIONS "-DBUILD_ASSIMP_SAMPLES:BOOL=${ASSIMP_BUILD_SAMPLES}")
SET(CPACK_DEBIAN_PACKAGE_SECTION "libs" )
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_COMPONENTS_ALL}")
SET(CPACK_DEBIAN_PACKAGE_SUGGESTS)
SET(CPACK_DEBIAN_PACKAGE_NAME "assimp")
SET(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES contrib/gtest contrib/zlib workspaces test doc obj samples packaging)
SET(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force)
SET(CPACK_DEBIAN_CHANGELOG)
execute_process(COMMAND lsb_release -is
OUTPUT_VARIABLE _lsb_distribution OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _lsb_release_failed)
SET(CPACK_DEBIAN_DISTRIBUTION_NAME ${_lsb_distribution} CACHE STRING "Name of the distrubiton")
STRING(TOLOWER ${CPACK_DEBIAN_DISTRIBUTION_NAME} CPACK_DEBIAN_DISTRIBUTION_NAME)
IF( ${CPACK_DEBIAN_DISTRIBUTION_NAME} STREQUAL "ubuntu" )
SET(CPACK_DEBIAN_DISTRIBUTION_RELEASES lucid maverick natty oneiric precise CACHE STRING "Release code-names of the distrubiton release")
ENDIF()
SET(DPUT_HOST "" CACHE STRING "PPA repository to upload the debian sources")
INCLUDE(CPack)
INCLUDE(DebSourcePPA)
ENDIF()
ENDIF()
if(WIN32)
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
SET(BIN_DIR "${PROJECT_SOURCE_DIR}/bin64/")
SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib64/")
else()
SET(BIN_DIR "${PROJECT_SOURCE_DIR}/bin32/")
SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib32/")
ENDIF()
IF(MSVC_TOOLSET_VERSION)
SET(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}")
SET(ASSIMP_MSVC_VERSION ${MSVC_PREFIX})
ELSE()
IF(MSVC12)
SET(ASSIMP_MSVC_VERSION "vc120")
ELSEIF(MSVC14)
SET(ASSIMP_MSVC_VERSION "vc140")
ELSEIF(MSVC15)
SET(ASSIMP_MSVC_VERSION "vc141")
ELSEIF(MSV142)
SET(ASSIMP_MSVC_VERSION "vc142")
ELSEIF(MSVC143)
SET(ASSIMP_MSVC_VERSION "vc143")
ENDIF()
ENDIF()
IF(MSVC12 OR MSVC14 OR MSVC15 )
ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
ELSE()
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
ENDIF()
ENDIF()
ENDIF ()

34
src/ThirdParty/assimp/CMakePresets.json vendored Normal file
View File

@@ -0,0 +1,34 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "assimp",
"binaryDir": "${sourceDir}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ASSIMP_BUILD_ASSIMP_TOOLS": "OFF"
}
},
{
"name": "assimp_double_precision",
"binaryDir": "${sourceDir}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ASSIMP_BUILD_ASSIMP_TOOLS": "OFF",
"ASSIMP_DOUBLE_PRECISION": "ON"
}
},
{
"name": "assimp_with_tools",
"binaryDir": "${sourceDir}",
"cacheVariables": {
"ASSIMP_BUILD_ASSIMP_TOOLS": "ON"
}
}
]
}

17
src/ThirdParty/assimp/Dockerfile vendored Normal file
View File

@@ -0,0 +1,17 @@
FROM gcc:1.5.1.0
RUN apt-get update \
apt-get install --no-install-recommends -y ninja-build cmake zlib1g-dev
WORKDIR /app
COPY . .
RUN mkdir build && cd build && \
cmake -G 'Ninja' \
-DCMAKE_BUILD_TYPE=Release \
-DASSIMP_BUILD_ASSIMP_TOOLS=ON \
.. && \
ninja -j4 && ninja install
CMD ["/app/build/bin/unit"]

17
src/ThirdParty/assimp/INSTALL vendored Normal file
View File

@@ -0,0 +1,17 @@
========================================================================
Open Asset Import Library (assimp) INSTALL
========================================================================
------------------------------
Getting the documentation
------------------------------
A regularly-updated copy is available at
https://assimp-docs.readthedocs.io/en/latest/
------------------------------
Building Assimp
------------------------------
Just check the build-instructions which you can find here: https://github.com/assimp/assimp/blob/master/Build.md

9
src/ThirdParty/assimp/assimp.pc.in vendored Normal file
View File

@@ -0,0 +1,9 @@
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @CMAKE_PROJECT_NAME@
Description: Import various well-known 3D model formats in an uniform manner.
Version: @PROJECT_VERSION@
Libs: -L${libdir} -lassimp@ASSIMP_LIBRARY_SUFFIX@
Libs.private: @LIBSTDC++_LIBRARIES@ @ZLIB_LIBRARIES_LINKED@
Cflags: -I${includedir}

View File

@@ -0,0 +1,126 @@
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
#
set(_CMAKE_SCRIPT_PATH ${CMAKE_CURRENT_LIST_DIR}) # must be outside coveralls_setup() to get correct path
#
# Param _COVERAGE_SRCS A list of source files that coverage should be collected for.
# Param _COVERALLS_UPLOAD Upload the result to coveralls?
#
function(coveralls_setup _COVERAGE_SRCS _COVERALLS_UPLOAD)
if (ARGC GREATER 2)
set(_CMAKE_SCRIPT_PATH ${ARGN})
message(STATUS "Coveralls: Using alternate CMake script dir: ${_CMAKE_SCRIPT_PATH}")
endif()
if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake")
message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake")
endif()
if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake")
message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake")
endif()
# When passing a CMake list to an external process, the list
# will be converted from the format "1;2;3" to "1 2 3".
# This means the script we're calling won't see it as a list
# of sources, but rather just one long path. We remedy this
# by replacing ";" with "*" and then reversing that in the script
# that we're calling.
# http://cmake.3232098.n2.nabble.com/Passing-a-CMake-list-quot-as-is-quot-to-a-custom-target-td6505681.html
set(COVERAGE_SRCS_TMP ${_COVERAGE_SRCS})
set(COVERAGE_SRCS "")
foreach (COVERAGE_SRC ${COVERAGE_SRCS_TMP})
set(COVERAGE_SRCS "${COVERAGE_SRCS}*${COVERAGE_SRC}")
endforeach()
#message("Coverage sources: ${COVERAGE_SRCS}")
set(COVERALLS_FILE ${PROJECT_BINARY_DIR}/coveralls.json)
add_custom_target(coveralls_generate
# Zero the coverage counters.
COMMAND ${CMAKE_COMMAND} -DPROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}" -P "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake"
# Run regress tests.
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
# Generate Gcov and translate it into coveralls JSON.
# We do this by executing an external CMake script.
# (We don't want this to run at CMake generation time, but after compilation and everything has run).
COMMAND ${CMAKE_COMMAND}
-DCOVERAGE_SRCS="${COVERAGE_SRCS}" # TODO: This is passed like: "a b c", not "a;b;c"
-DCOVERALLS_OUTPUT_FILE="${COVERALLS_FILE}"
-DCOV_PATH="${PROJECT_BINARY_DIR}"
-DPROJECT_ROOT="${PROJECT_SOURCE_DIR}"
-P "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake"
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
COMMENT "Generating coveralls output..."
)
if (_COVERALLS_UPLOAD)
message("COVERALLS UPLOAD: ON")
find_program(CURL_EXECUTABLE curl)
if (NOT CURL_EXECUTABLE)
message(FATAL_ERROR "Coveralls: curl not found! Aborting")
endif()
add_custom_target(coveralls_upload
# Upload the JSON to coveralls.
COMMAND ${CURL_EXECUTABLE}
-S -F json_file=@${COVERALLS_FILE}
https://coveralls.io/api/v1/jobs
DEPENDS coveralls_generate
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
COMMENT "Uploading coveralls output...")
add_custom_target(coveralls DEPENDS coveralls_upload)
else()
message("COVERALLS UPLOAD: OFF")
add_custom_target(coveralls DEPENDS coveralls_generate)
endif()
endfunction()
macro(coveralls_turn_on_coverage)
if(NOT (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
AND (NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang"))
message(FATAL_ERROR "Coveralls: Compiler ${CMAKE_C_COMPILER_ID} is not GNU gcc! Aborting... You can set this on the command line using CC=/usr/bin/gcc CXX=/usr/bin/g++ cmake <options> ..")
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
message(FATAL_ERROR "Coveralls: Code coverage results with an optimised (non-Debug) build may be misleading! Add -DCMAKE_BUILD_TYPE=Debug")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
endmacro()

View File

@@ -0,0 +1,31 @@
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
#
# do not follow symlinks in file(GLOB_RECURSE ...)
cmake_policy(SET CMP0009 NEW)
file(GLOB_RECURSE GCDA_FILES "${PROJECT_BINARY_DIR}/*.gcda")
if(NOT GCDA_FILES STREQUAL "")
file(REMOVE ${GCDA_FILES})
endif()

View File

@@ -0,0 +1,482 @@
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
#
# This is intended to be run by a custom target in a CMake project like this.
# 0. Compile program with coverage support.
# 1. Clear coverage data. (Recursively delete *.gcda in build dir)
# 2. Run the unit tests.
# 3. Run this script specifying which source files the coverage should be performed on.
#
# This script will then use gcov to generate .gcov files in the directory specified
# via the COV_PATH var. This should probably be the same as your cmake build dir.
#
# It then parses the .gcov files to convert them into the Coveralls JSON format:
# https://coveralls.io/docs/api
#
# Example for running as standalone CMake script from the command line:
# (Note it is important the -P is at the end...)
# $ cmake -DCOV_PATH=$(pwd)
# -DCOVERAGE_SRCS="catcierge_rfid.c;catcierge_timer.c"
# -P ../cmake/CoverallsGcovUpload.cmake
#
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
#
# Make sure we have the needed arguments.
#
if (NOT COVERALLS_OUTPUT_FILE)
message(FATAL_ERROR "Coveralls: No coveralls output file specified. Please set COVERALLS_OUTPUT_FILE")
endif()
if (NOT COV_PATH)
message(FATAL_ERROR "Coveralls: Missing coverage directory path where gcov files will be generated. Please set COV_PATH")
endif()
if (NOT COVERAGE_SRCS)
message(FATAL_ERROR "Coveralls: Missing the list of source files that we should get the coverage data for COVERAGE_SRCS")
endif()
if (NOT PROJECT_ROOT)
message(FATAL_ERROR "Coveralls: Missing PROJECT_ROOT.")
endif()
# Since it's not possible to pass a CMake list properly in the
# "1;2;3" format to an external process, we have replaced the
# ";" with "*", so reverse that here so we get it back into the
# CMake list format.
string(REGEX REPLACE "\\*" ";" COVERAGE_SRCS ${COVERAGE_SRCS})
if (NOT DEFINED ENV{GCOV})
find_program(GCOV_EXECUTABLE gcov)
else()
find_program(GCOV_EXECUTABLE $ENV{GCOV})
endif()
# convert all paths in COVERAGE_SRCS to absolute paths
set(COVERAGE_SRCS_TMP "")
foreach (COVERAGE_SRC ${COVERAGE_SRCS})
if (NOT "${COVERAGE_SRC}" MATCHES "^/")
set(COVERAGE_SRC ${PROJECT_ROOT}/${COVERAGE_SRC})
endif()
list(APPEND COVERAGE_SRCS_TMP ${COVERAGE_SRC})
endforeach()
set(COVERAGE_SRCS ${COVERAGE_SRCS_TMP})
unset(COVERAGE_SRCS_TMP)
if (NOT GCOV_EXECUTABLE)
message(FATAL_ERROR "gcov not found! Aborting...")
endif()
find_package(Git)
set(JSON_REPO_TEMPLATE
"{
\"head\": {
\"id\": \"\@GIT_COMMIT_HASH\@\",
\"author_name\": \"\@GIT_AUTHOR_NAME\@\",
\"author_email\": \"\@GIT_AUTHOR_EMAIL\@\",
\"committer_name\": \"\@GIT_COMMITTER_NAME\@\",
\"committer_email\": \"\@GIT_COMMITTER_EMAIL\@\",
\"message\": \"\@GIT_COMMIT_MESSAGE\@\"
},
\"branch\": \"@GIT_BRANCH@\",
\"remotes\": []
}"
)
# TODO: Fill in git remote data
if (GIT_FOUND)
# Branch.
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
macro (git_log_format FORMAT_CHARS VAR_NAME)
execute_process(
COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%${FORMAT_CHARS}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE ${VAR_NAME}
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endmacro()
git_log_format(an GIT_AUTHOR_NAME)
git_log_format(ae GIT_AUTHOR_EMAIL)
git_log_format(cn GIT_COMMITTER_NAME)
git_log_format(ce GIT_COMMITTER_EMAIL)
git_log_format(B GIT_COMMIT_MESSAGE)
git_log_format(H GIT_COMMIT_HASH)
if(GIT_COMMIT_MESSAGE)
string(REPLACE "\n" "\\n" GIT_COMMIT_MESSAGE ${GIT_COMMIT_MESSAGE})
endif()
message("Git exe: ${GIT_EXECUTABLE}")
message("Git branch: ${GIT_BRANCH}")
message("Git author: ${GIT_AUTHOR_NAME}")
message("Git e-mail: ${GIT_AUTHOR_EMAIL}")
message("Git commiter name: ${GIT_COMMITTER_NAME}")
message("Git commiter e-mail: ${GIT_COMMITTER_EMAIL}")
message("Git commit hash: ${GIT_COMMIT_HASH}")
message("Git commit message: ${GIT_COMMIT_MESSAGE}")
string(CONFIGURE ${JSON_REPO_TEMPLATE} JSON_REPO_DATA)
else()
set(JSON_REPO_DATA "{}")
endif()
############################# Macros #########################################
#
# This macro converts from the full path format gcov outputs:
#
# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
#
# to the original source file path the .gcov is for:
#
# /path/to/project/root/subdir/the_file.c
#
macro(get_source_path_from_gcov_filename _SRC_FILENAME _GCOV_FILENAME)
# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
# ->
# #path#to#project#root#subdir#the_file.c.gcov
get_filename_component(_GCOV_FILENAME_WEXT ${_GCOV_FILENAME} NAME)
# #path#to#project#root#subdir#the_file.c.gcov -> /path/to/project/root/subdir/the_file.c
string(REGEX REPLACE "\\.gcov$" "" SRC_FILENAME_TMP ${_GCOV_FILENAME_WEXT})
string(REGEX REPLACE "\#" "/" SRC_FILENAME_TMP ${SRC_FILENAME_TMP})
set(${_SRC_FILENAME} "${SRC_FILENAME_TMP}")
endmacro()
##############################################################################
# Get the coverage data.
file(GLOB_RECURSE GCDA_FILES "${COV_PATH}/*.gcda")
message("GCDA files:")
# Get a list of all the object directories needed by gcov
# (The directories the .gcda files and .o files are found in)
# and run gcov on those.
foreach(GCDA ${GCDA_FILES})
message("Process: ${GCDA}")
message("------------------------------------------------------------------------------")
get_filename_component(GCDA_DIR ${GCDA} PATH)
#
# The -p below refers to "Preserve path components",
# This means that the generated gcov filename of a source file will
# keep the original files entire filepath, but / is replaced with #.
# Example:
#
# /path/to/project/root/build/CMakeFiles/the_file.dir/subdir/the_file.c.gcda
# ------------------------------------------------------------------------------
# File '/path/to/project/root/subdir/the_file.c'
# Lines executed:68.34% of 199
# /path/to/project/root/subdir/the_file.c:creating '#path#to#project#root#subdir#the_file.c.gcov'
#
# If -p is not specified then the file is named only "the_file.c.gcov"
#
execute_process(
COMMAND ${GCOV_EXECUTABLE} -p -o ${GCDA_DIR} ${GCDA}
WORKING_DIRECTORY ${COV_PATH}
)
endforeach()
# TODO: Make these be absolute path
file(GLOB ALL_GCOV_FILES ${COV_PATH}/*.gcov)
# Get only the filenames to use for filtering.
#set(COVERAGE_SRCS_NAMES "")
#foreach (COVSRC ${COVERAGE_SRCS})
# get_filename_component(COVSRC_NAME ${COVSRC} NAME)
# message("${COVSRC} -> ${COVSRC_NAME}")
# list(APPEND COVERAGE_SRCS_NAMES "${COVSRC_NAME}")
#endforeach()
#
# Filter out all but the gcov files we want.
#
# We do this by comparing the list of COVERAGE_SRCS filepaths that the
# user wants the coverage data for with the paths of the generated .gcov files,
# so that we only keep the relevant gcov files.
#
# Example:
# COVERAGE_SRCS =
# /path/to/project/root/subdir/the_file.c
#
# ALL_GCOV_FILES =
# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
# /path/to/project/root/build/#path#to#project#root#subdir#other_file.c.gcov
#
# Result should be:
# GCOV_FILES =
# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
#
set(GCOV_FILES "")
#message("Look in coverage sources: ${COVERAGE_SRCS}")
message("\nFilter out unwanted GCOV files:")
message("===============================")
set(COVERAGE_SRCS_REMAINING ${COVERAGE_SRCS})
foreach (GCOV_FILE ${ALL_GCOV_FILES})
#
# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
# ->
# /path/to/project/root/subdir/the_file.c
get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE})
file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}")
# Is this in the list of source files?
# TODO: We want to match against relative path filenames from the source file root...
list(FIND COVERAGE_SRCS ${GCOV_SRC_PATH} WAS_FOUND)
if (NOT WAS_FOUND EQUAL -1)
message("YES: ${GCOV_FILE}")
list(APPEND GCOV_FILES ${GCOV_FILE})
# We remove it from the list, so we don't bother searching for it again.
# Also files left in COVERAGE_SRCS_REMAINING after this loop ends should
# have coverage data generated from them (no lines are covered).
list(REMOVE_ITEM COVERAGE_SRCS_REMAINING ${GCOV_SRC_PATH})
else()
message("NO: ${GCOV_FILE}")
endif()
endforeach()
# TODO: Enable setting these
set(JSON_SERVICE_NAME "travis-ci")
set(JSON_SERVICE_JOB_ID $ENV{TRAVIS_JOB_ID})
set(JSON_REPO_TOKEN $ENV{COVERALLS_REPO_TOKEN})
set(JSON_TEMPLATE
"{
\"repo_token\": \"\@JSON_REPO_TOKEN\@\",
\"service_name\": \"\@JSON_SERVICE_NAME\@\",
\"service_job_id\": \"\@JSON_SERVICE_JOB_ID\@\",
\"source_files\": \@JSON_GCOV_FILES\@,
\"git\": \@JSON_REPO_DATA\@
}"
)
set(SRC_FILE_TEMPLATE
"{
\"name\": \"\@GCOV_SRC_REL_PATH\@\",
\"source_digest\": \"\@GCOV_CONTENTS_MD5\@\",
\"coverage\": \@GCOV_FILE_COVERAGE\@
}"
)
message("\nGenerate JSON for files:")
message("=========================")
set(JSON_GCOV_FILES "[")
# Read the GCOV files line by line and get the coverage data.
foreach (GCOV_FILE ${GCOV_FILES})
get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE})
file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}")
# The new coveralls API doesn't need the entire source (Yay!)
# However, still keeping that part for now. Will cleanup in the future.
file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5)
message("MD5: ${GCOV_SRC_PATH} = ${GCOV_CONTENTS_MD5}")
# Loads the gcov file as a list of lines.
# (We first open the file and replace all occurrences of [] with _
# because CMake will fail to parse a line containing unmatched brackets...
# also the \ to escaped \n in macros screws up things.)
# https://public.kitware.com/Bug/view.php?id=15369
file(READ ${GCOV_FILE} GCOV_CONTENTS)
string(REPLACE "[" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
string(REPLACE "]" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
string(REPLACE "\\" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
# Remove file contents to avoid encoding issues (cmake 2.8 has no ENCODING option)
string(REGEX REPLACE "([^:]*):([^:]*):([^\n]*)\n" "\\1:\\2: \n" GCOV_CONTENTS "${GCOV_CONTENTS}")
file(WRITE ${GCOV_FILE}_tmp "${GCOV_CONTENTS}")
file(STRINGS ${GCOV_FILE}_tmp GCOV_LINES)
list(LENGTH GCOV_LINES LINE_COUNT)
# Instead of trying to parse the source from the
# gcov file, simply read the file contents from the source file.
# (Parsing it from the gcov is hard because C-code uses ; in many places
# which also happens to be the same as the CMake list delimiter).
file(READ ${GCOV_SRC_PATH} GCOV_FILE_SOURCE)
string(REPLACE "\\" "\\\\" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
string(REGEX REPLACE "\"" "\\\\\"" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
string(REPLACE "\t" "\\\\t" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
string(REPLACE "\r" "\\\\r" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
string(REPLACE "\n" "\\\\n" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
# According to http://json.org/ these should be escaped as well.
# Don't know how to do that in CMake however...
#string(REPLACE "\b" "\\\\b" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
#string(REPLACE "\f" "\\\\f" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
#string(REGEX REPLACE "\u([a-fA-F0-9]{4})" "\\\\u\\1" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
# We want a json array of coverage data as a single string
# start building them from the contents of the .gcov
set(GCOV_FILE_COVERAGE "[")
set(GCOV_LINE_COUNT 1) # Line number for the .gcov.
set(DO_SKIP 0)
foreach (GCOV_LINE ${GCOV_LINES})
#message("${GCOV_LINE}")
# Example of what we're parsing:
# Hitcount |Line | Source
# " 8: 26: if (!allowed || (strlen(allowed) == 0))"
string(REGEX REPLACE
"^([^:]*):([^:]*):(.*)$"
"\\1;\\2;\\3"
RES
"${GCOV_LINE}")
# Check if we should exclude lines using the Lcov syntax.
string(REGEX MATCH "LCOV_EXCL_START" START_SKIP "${GCOV_LINE}")
string(REGEX MATCH "LCOV_EXCL_END" END_SKIP "${GCOV_LINE}")
string(REGEX MATCH "LCOV_EXCL_LINE" LINE_SKIP "${GCOV_LINE}")
set(RESET_SKIP 0)
if (LINE_SKIP AND NOT DO_SKIP)
set(DO_SKIP 1)
set(RESET_SKIP 1)
endif()
if (START_SKIP)
set(DO_SKIP 1)
message("${GCOV_LINE_COUNT}: Start skip")
endif()
if (END_SKIP)
set(DO_SKIP 0)
endif()
list(LENGTH RES RES_COUNT)
if (RES_COUNT GREATER 2)
list(GET RES 0 HITCOUNT)
list(GET RES 1 LINE)
list(GET RES 2 SOURCE)
string(STRIP ${HITCOUNT} HITCOUNT)
string(STRIP ${LINE} LINE)
# Lines with 0 line numbers are metadata and can be ignored.
if (NOT ${LINE} EQUAL 0)
if (DO_SKIP)
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ")
else()
# Translate the hitcount into valid JSON values.
if (${HITCOUNT} STREQUAL "#####" OR ${HITCOUNT} STREQUAL "=====")
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}0, ")
elseif (${HITCOUNT} STREQUAL "-")
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ")
else()
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}${HITCOUNT}, ")
endif()
endif()
endif()
else()
message(WARNING "Failed to properly parse line (RES_COUNT = ${RES_COUNT}) ${GCOV_FILE}:${GCOV_LINE_COUNT}\n-->${GCOV_LINE}")
endif()
if (RESET_SKIP)
set(DO_SKIP 0)
endif()
math(EXPR GCOV_LINE_COUNT "${GCOV_LINE_COUNT}+1")
endforeach()
message("${GCOV_LINE_COUNT} of ${LINE_COUNT} lines read!")
# Advanced way of removing the trailing comma in the JSON array.
# "[1, 2, 3, " -> "[1, 2, 3"
string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE})
# Append the trailing ] to complete the JSON array.
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]")
# Generate the final JSON for this file.
message("Generate JSON for file: ${GCOV_SRC_REL_PATH}...")
string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON)
set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ")
endforeach()
# Loop through all files we couldn't find any coverage for
# as well, and generate JSON for those as well with 0% coverage.
foreach(NOT_COVERED_SRC ${COVERAGE_SRCS_REMAINING})
# Set variables for json replacement
set(GCOV_SRC_PATH ${NOT_COVERED_SRC})
file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5)
file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}")
# Loads the source file as a list of lines.
file(STRINGS ${NOT_COVERED_SRC} SRC_LINES)
set(GCOV_FILE_COVERAGE "[")
set(GCOV_FILE_SOURCE "")
foreach (SOURCE ${SRC_LINES})
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ")
string(REPLACE "\\" "\\\\" SOURCE "${SOURCE}")
string(REGEX REPLACE "\"" "\\\\\"" SOURCE "${SOURCE}")
string(REPLACE "\t" "\\\\t" SOURCE "${SOURCE}")
string(REPLACE "\r" "\\\\r" SOURCE "${SOURCE}")
set(GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}${SOURCE}\\n")
endforeach()
# Remove trailing comma, and complete JSON array with ]
string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE})
set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]")
# Generate the final JSON for this file.
message("Generate JSON for non-gcov file: ${NOT_COVERED_SRC}...")
string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON)
set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ")
endforeach()
# Get rid of trailing comma.
string(REGEX REPLACE ",[ ]*$" "" JSON_GCOV_FILES ${JSON_GCOV_FILES})
set(JSON_GCOV_FILES "${JSON_GCOV_FILES}]")
# Generate the final complete JSON!
message("Generate final JSON...")
string(CONFIGURE ${JSON_TEMPLATE} JSON)
file(WRITE "${COVERALLS_OUTPUT_FILE}" "${JSON}")
message("###########################################################################")
message("Generated coveralls JSON containing coverage data:")
message("${COVERALLS_OUTPUT_FILE}")
message("###########################################################################")

View File

@@ -0,0 +1,346 @@
## Debian Source Package Generator
#
# Copyright (c) 2010 Daniel Pfeifer <daniel@pfeifer-mail.de>
# Many modifications by Rosen Diankov <rosen.diankov@gmail.com>
#
# Creates source debian files and manages library dependencies
#
# Features:
#
# - Automatically generates symbols and run-time dependencies from the build dependencies
# - Custom copy of source directory via CPACK_DEBIAN_PACKAGE_SOURCE_COPY
# - Simultaneous output of multiple debian source packages for each distribution
# - Can specificy distribution-specific dependencies by suffixing DEPENDS with _${DISTRO_NAME}, for example: CPACK_DEBIAN_PACKAGE_DEPENDS_LUCID, CPACK_COMPONENT_MYCOMP0_DEPENDS_LUCID
#
# Usage:
#
# set(CPACK_DEBIAN_BUILD_DEPENDS debhelper cmake)
# set(CPACK_DEBIAN_PACKAGE_PRIORITY optional)
# set(CPACK_DEBIAN_PACKAGE_SECTION devel)
# set(CPACK_DEBIAN_CMAKE_OPTIONS "-DMYOPTION=myvalue")
# set(CPACK_DEBIAN_PACKAGE_DEPENDS mycomp0 mycomp1 some_ubuntu_package)
# set(CPACK_DEBIAN_PACKAGE_DEPENDS_UBUNTU_LUCID mycomp0 mycomp1 lucid_specific_package)
# set(CPACK_DEBIAN_PACKAGE_NAME mypackage)
# set(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES unnecessary_file unnecessary_dir/file0)
# set(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force) # if using subversion
# set(CPACK_DEBIAN_DISTRIBUTION_NAME ubuntu)
# set(CPACK_DEBIAN_DISTRIBUTION_RELEASES karmic lucid maverick natty)
# set(CPACK_DEBIAN_CHANGELOG " * Extra change log lines")
# set(CPACK_DEBIAN_PACKAGE_SUGGESTS "ipython")
# set(CPACK_COMPONENT_X_RECOMMENDS "recommended-package")
##
find_program(DEBUILD_EXECUTABLE debuild)
find_program(DPUT_EXECUTABLE dput)
if(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE)
return()
endif(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE)
# DEBIAN/control
# debian policy enforce lower case for package name
# Package: (mandatory)
IF(NOT CPACK_DEBIAN_PACKAGE_NAME)
STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_DEBIAN_PACKAGE_NAME)
ENDIF(NOT CPACK_DEBIAN_PACKAGE_NAME)
# Section: (recommended)
IF(NOT CPACK_DEBIAN_PACKAGE_SECTION)
SET(CPACK_DEBIAN_PACKAGE_SECTION "devel")
ENDIF(NOT CPACK_DEBIAN_PACKAGE_SECTION)
# Priority: (recommended)
IF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY)
SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
ENDIF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY)
file(STRINGS ${CPACK_PACKAGE_DESCRIPTION_FILE} DESC_LINES)
foreach(LINE ${DESC_LINES})
set(DEB_LONG_DESCRIPTION "${DEB_LONG_DESCRIPTION} ${LINE}\n")
endforeach(LINE ${DESC_LINES})
file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/Debian")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/Debian")
set(DEBIAN_SOURCE_ORIG_DIR "${CMAKE_BINARY_DIR}/Debian/${CPACK_DEBIAN_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
if( CPACK_DEBIAN_PACKAGE_SOURCE_COPY )
execute_process(COMMAND ${CPACK_DEBIAN_PACKAGE_SOURCE_COPY} "${CMAKE_SOURCE_DIR}" "${DEBIAN_SOURCE_ORIG_DIR}.orig")
else()
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR} "${DEBIAN_SOURCE_ORIG_DIR}.orig")
execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory "${DEBIAN_SOURCE_ORIG_DIR}.orig/.git")
execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory "${DEBIAN_SOURCE_ORIG_DIR}.orig/.svn")
endif()
# remove unnecessary folders
foreach(REMOVE_DIR ${CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES})
file(REMOVE_RECURSE ${DEBIAN_SOURCE_ORIG_DIR}.orig/${REMOVE_DIR})
endforeach()
# create the original source tar
execute_process(COMMAND ${CMAKE_COMMAND} -E tar czf "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}.orig.tar.gz" "${CPACK_DEBIAN_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.orig" WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian)
set(DEB_SOURCE_CHANGES)
foreach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES})
set(DEBIAN_SOURCE_DIR "${DEBIAN_SOURCE_ORIG_DIR}-${CPACK_DEBIAN_DISTRIBUTION_NAME}1~${RELEASE}1")
set(RELEASE_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}-${CPACK_DEBIAN_DISTRIBUTION_NAME}1~${RELEASE}1")
string(TOUPPER ${RELEASE} RELEASE_UPPER)
string(TOUPPER ${CPACK_DEBIAN_DISTRIBUTION_NAME} DISTRIBUTION_NAME_UPPER)
file(MAKE_DIRECTORY ${DEBIAN_SOURCE_DIR}/debian)
##############################################################################
# debian/control
set(DEBIAN_CONTROL ${DEBIAN_SOURCE_DIR}/debian/control)
file(WRITE ${DEBIAN_CONTROL}
"Source: ${CPACK_DEBIAN_PACKAGE_NAME}\n"
"Section: ${CPACK_DEBIAN_PACKAGE_SECTION}\n"
"Priority: ${CPACK_DEBIAN_PACKAGE_PRIORITY}\n"
"DM-Upload-Allowed: yes\n"
"Maintainer: ${CPACK_PACKAGE_CONTACT}\n"
"Build-Depends: "
)
if( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS})
endif( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
file(APPEND ${DEBIAN_CONTROL} "\n"
"Standards-Version: 3.8.4\n"
"Homepage: ${CPACK_PACKAGE_VENDOR}\n"
"\n"
"Package: ${CPACK_DEBIAN_PACKAGE_NAME}\n"
"Architecture: any\n"
"Depends: "
)
if( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS})
endif( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
file(APPEND ${DEBIAN_CONTROL} "\nRecommends: ")
if( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS})
endif( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
file(APPEND ${DEBIAN_CONTROL} "\nSuggests: ")
if( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS})
file(APPEND ${DEBIAN_CONTROL} "${DEP}, ")
endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS})
endif( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
file(APPEND ${DEBIAN_CONTROL} "\n"
"Description: ${CPACK_PACKAGE_DISPLAY_NAME} ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n"
"${DEB_LONG_DESCRIPTION}"
)
foreach(COMPONENT ${CPACK_COMPONENTS_ALL})
string(TOUPPER ${COMPONENT} UPPER_COMPONENT)
set(DEPENDS "\${shlibs:Depends}")
if( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
set(DEPENDS "${DEPENDS}, ${DEP}")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
set(DEPENDS "${DEPENDS}, ${DEP}")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS})
set(DEPENDS "${DEPENDS}, ${DEP}")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS})
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
set(RECOMMENDS)
if( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
set(RECOMMENDS "${RECOMMENDS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}})
set(RECOMMENDS "${RECOMMENDS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS})
set(RECOMMENDS "${RECOMMENDS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS})
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
set(SUGGESTS)
if( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
set(SUGGESTS "${SUGGESTS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
if( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}})
set(SUGGESTS "${SUGGESTS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}})
else( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS})
set(SUGGESTS "${SUGGESTS} ${DEP}, ")
endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS})
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} )
endif( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} )
file(APPEND ${DEBIAN_CONTROL} "\n"
"Package: ${COMPONENT}\n"
"Architecture: any\n"
"Depends: ${DEPENDS}\n"
"Recommends: ${RECOMMENDS}\n"
"Suggests: ${SUGGESTS}\n"
"Description: ${CPACK_PACKAGE_DISPLAY_NAME} ${CPACK_COMPONENT_${UPPER_COMPONENT}_DISPLAY_NAME}\n"
"${DEB_LONG_DESCRIPTION}"
" .\n"
" ${CPACK_COMPONENT_${UPPER_COMPONENT}_DESCRIPTION}\n"
)
endforeach(COMPONENT ${CPACK_COMPONENTS_ALL})
##############################################################################
# debian/copyright
set(DEBIAN_COPYRIGHT ${DEBIAN_SOURCE_DIR}/debian/copyright)
execute_process(COMMAND ${CMAKE_COMMAND} -E
copy ${CPACK_RESOURCE_FILE_LICENSE} ${DEBIAN_COPYRIGHT}
)
##############################################################################
# debian/rules
set(DEBIAN_RULES ${DEBIAN_SOURCE_DIR}/debian/rules)
file(WRITE ${DEBIAN_RULES}
"#!/usr/bin/make -f\n"
"\n"
"BUILDDIR = build_dir\n"
"\n"
"build:\n"
" mkdir $(BUILDDIR)\n"
" cd $(BUILDDIR); cmake -DCMAKE_BUILD_TYPE=Release ${CPACK_DEBIAN_CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX=/usr ..\n"
" $(MAKE) -C $(BUILDDIR) preinstall\n"
" touch build\n"
"\n"
"binary: binary-indep binary-arch\n"
"\n"
"binary-indep: build\n"
"\n"
"binary-arch: build\n"
" cd $(BUILDDIR); cmake -DCOMPONENT=Unspecified -DCMAKE_INSTALL_PREFIX=../debian/tmp/usr -P cmake_install.cmake\n"
" mkdir -p debian/tmp/DEBIAN\n"
" dpkg-gensymbols -p${CPACK_DEBIAN_PACKAGE_NAME}\n"
)
foreach(COMPONENT ${CPACK_COMPONENTS_ALL})
set(PATH debian/${COMPONENT})
file(APPEND ${DEBIAN_RULES}
" cd $(BUILDDIR); cmake -DCOMPONENT=${COMPONENT} -DCMAKE_INSTALL_PREFIX=../${PATH}/usr -P cmake_install.cmake\n"
" mkdir -p ${PATH}/DEBIAN\n"
" dpkg-gensymbols -p${COMPONENT} -P${PATH}\n"
)
endforeach(COMPONENT ${CPACK_COMPONENTS_ALL})
file(APPEND ${DEBIAN_RULES}
" dh_shlibdeps\n"
" dh_strip\n" # for reducing size
" dpkg-gencontrol -p${CPACK_DEBIAN_PACKAGE_NAME}\n"
" dpkg --build debian/tmp ..\n"
)
foreach(COMPONENT ${CPACK_COMPONENTS_ALL})
set(PATH debian/${COMPONENT})
file(APPEND ${DEBIAN_RULES}
" dpkg-gencontrol -p${COMPONENT} -P${PATH} -Tdebian/${COMPONENT}.substvars\n"
" dpkg --build ${PATH} ..\n"
)
endforeach(COMPONENT ${CPACK_COMPONENTS_ALL})
file(APPEND ${DEBIAN_RULES}
"\n"
"clean:\n"
" rm -f build\n"
" rm -rf $(BUILDDIR)\n"
"\n"
".PHONY: binary binary-arch binary-indep clean\n"
)
execute_process(COMMAND chmod +x ${DEBIAN_RULES})
##############################################################################
# debian/compat
file(WRITE ${DEBIAN_SOURCE_DIR}/debian/compat "7")
##############################################################################
# debian/source/format
file(WRITE ${DEBIAN_SOURCE_DIR}/debian/source/format "3.0 (quilt)")
##############################################################################
# debian/changelog
set(DEBIAN_CHANGELOG ${DEBIAN_SOURCE_DIR}/debian/changelog)
execute_process(COMMAND date -R OUTPUT_VARIABLE DATE_TIME)
file(WRITE ${DEBIAN_CHANGELOG}
"${CPACK_DEBIAN_PACKAGE_NAME} (${RELEASE_PACKAGE_VERSION}) ${RELEASE}; urgency=medium\n\n"
" * Package built with CMake\n\n"
"${CPACK_DEBIAN_CHANGELOG}"
" -- ${CPACK_PACKAGE_CONTACT} ${DATE_TIME}"
)
##############################################################################
# debuild -S
if( DEB_SOURCE_CHANGES )
set(DEBUILD_OPTIONS "-sd")
else()
set(DEBUILD_OPTIONS "-sa")
endif()
set(SOURCE_CHANGES_FILE "${CPACK_DEBIAN_PACKAGE_NAME}_${RELEASE_PACKAGE_VERSION}_source.changes")
set(DEB_SOURCE_CHANGES ${DEB_SOURCE_CHANGES} "${SOURCE_CHANGES_FILE}")
add_custom_command(OUTPUT "${SOURCE_CHANGES_FILE}" COMMAND ${DEBUILD_EXECUTABLE} -S ${DEBUILD_OPTIONS} WORKING_DIRECTORY ${DEBIAN_SOURCE_DIR})
endforeach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES})
##############################################################################
# dput ppa:your-lp-id/ppa <source.changes>
add_custom_target(dput ${DPUT_EXECUTABLE} ${DPUT_HOST} ${DEB_SOURCE_CHANGES} DEPENDS ${DEB_SOURCE_CHANGES} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian)

View File

@@ -0,0 +1,72 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#.rst:
# FindDevIL
# ---------
#
#
#
# This module locates the developer's image library.
# http://openil.sourceforge.net/
#
# This module sets:
#
# ::
#
# IL_LIBRARIES - the name of the IL library. These include the full path to
# the core DevIL library. This one has to be linked into the
# application.
# ILU_LIBRARIES - the name of the ILU library. Again, the full path. This
# library is for filters and effects, not actual loading. It
# doesn't have to be linked if the functionality it provides
# is not used.
# ILUT_LIBRARIES - the name of the ILUT library. Full path. This part of the
# library interfaces with OpenGL. It is not strictly needed
# in applications.
# IL_INCLUDE_DIR - where to find the il.h, ilu.h and ilut.h files.
# IL_FOUND - this is set to TRUE if all the above variables were set.
# This will be set to false if ILU or ILUT are not found,
# even if they are not needed. In most systems, if one
# library is found all the others are as well. That's the
# way the DevIL developers release it.
# TODO: Add version support.
# Tested under Linux and Windows (MSVC)
#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
include(FindPackageHandleStandardArgs)
find_path(IL_INCLUDE_DIR il.h
PATH_SUFFIXES include IL
DOC "The path to the directory that contains il.h"
)
#message("IL_INCLUDE_DIR is ${IL_INCLUDE_DIR}")
find_library(IL_LIBRARIES
NAMES IL DEVIL
PATH_SUFFIXES lib64 lib lib32
DOC "The file that corresponds to the base il library."
)
#message("IL_LIBRARIES is ${IL_LIBRARIES}")
find_library(ILUT_LIBRARIES
NAMES ILUT
PATH_SUFFIXES lib64 lib lib32
DOC "The file that corresponds to the il (system?) utility library."
)
#message("ILUT_LIBRARIES is ${ILUT_LIBRARIES}")
find_library(ILU_LIBRARIES
NAMES ILU
PATH_SUFFIXES lib64 lib lib32
DOC "The file that corresponds to the il utility library."
)
#message("ILU_LIBRARIES is ${ILU_LIBRARIES}")
FIND_PACKAGE_HANDLE_STANDARD_ARGS(IL DEFAULT_MSG
IL_LIBRARIES IL_INCLUDE_DIR)

View File

@@ -0,0 +1,101 @@
#-------------------------------------------------------------------
# This file is part of the CMake build system for OGRE
# (Object-oriented Graphics Rendering Engine)
# For the latest info, see http://www.ogre3d.org/
#
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Find DirectX SDK
# Define:
# DirectX_FOUND
# DirectX_INCLUDE_DIR
# DirectX_LIBRARY
# DirectX_ROOT_DIR
if(WIN32) # The only platform it makes sense to check for DirectX SDK
include(FindPkgMacros)
findpkg_begin(DirectX)
# Get path, convert backslashes as ${ENV_DXSDK_DIR}
getenv_path(DXSDK_DIR)
getenv_path(DIRECTX_HOME)
getenv_path(DIRECTX_ROOT)
getenv_path(DIRECTX_BASE)
# construct search paths
set(DirectX_PREFIX_PATH
"${DXSDK_DIR}" "${ENV_DXSDK_DIR}"
"${DIRECTX_HOME}" "${ENV_DIRECTX_HOME}"
"${DIRECTX_ROOT}" "${ENV_DIRECTX_ROOT}"
"${DIRECTX_BASE}" "${ENV_DIRECTX_BASE}"
"C:/apps_x86/Microsoft DirectX SDK*"
"C:/Program Files (x86)/Microsoft DirectX SDK*"
"C:/apps/Microsoft DirectX SDK*"
"C:/Program Files/Microsoft DirectX SDK*"
"C:/Program Files (x86)/Windows Kits/8.1"
"$ENV{ProgramFiles}/Microsoft DirectX SDK*"
)
create_search_paths(DirectX)
# redo search if prefix path changed
clear_if_changed(DirectX_PREFIX_PATH
DirectX_LIBRARY
DirectX_INCLUDE_DIR
)
find_path(DirectX_INCLUDE_DIR NAMES d3d9.h HINTS ${DirectX_INC_SEARCH_PATH})
# dlls are in DirectX_ROOT_DIR/Developer Runtime/x64|x86
# lib files are in DirectX_ROOT_DIR/Lib/x64|x86
if(CMAKE_CL_64)
set(DirectX_LIBPATH_SUFFIX "x64")
else(CMAKE_CL_64)
set(DirectX_LIBPATH_SUFFIX "x86")
endif(CMAKE_CL_64)
find_library(DirectX_LIBRARY NAMES d3d9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
find_library(DirectX_D3DX9_LIBRARY NAMES d3dx9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
find_library(DirectX_DXERR_LIBRARY NAMES DxErr DxErr9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
find_library(DirectX_DXGUID_LIBRARY NAMES dxguid HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
# look for dxgi (needed by both 10 and 11)
find_library(DirectX_DXGI_LIBRARY NAMES dxgi HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
# look for d3dcompiler (needed by 11)
find_library(DirectX_D3DCOMPILER_LIBRARY NAMES d3dcompiler HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
findpkg_finish(DirectX)
set(DirectX_LIBRARIES ${DirectX_LIBRARIES}
${DirectX_D3DX9_LIBRARY}
${DirectX_DXERR_LIBRARY}
${DirectX_DXGUID_LIBRARY}
)
mark_as_advanced(DirectX_D3DX9_LIBRARY DirectX_DXERR_LIBRARY DirectX_DXGUID_LIBRARY
DirectX_DXGI_LIBRARY DirectX_D3DCOMPILER_LIBRARY)
# look for D3D11 components
if (DirectX_FOUND)
find_path(DirectX_D3D11_INCLUDE_DIR NAMES D3D11Shader.h HINTS ${DirectX_INC_SEARCH_PATH})
get_filename_component(DirectX_LIBRARY_DIR "${DirectX_LIBRARY}" PATH)
message(STATUS "DX lib dir: ${DirectX_LIBRARY_DIR}")
find_library(DirectX_D3D11_LIBRARY NAMES d3d11 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
find_library(DirectX_D3DX11_LIBRARY NAMES d3dx11 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX})
if (DirectX_D3D11_INCLUDE_DIR AND DirectX_D3D11_LIBRARY)
set(DirectX_D3D11_FOUND TRUE)
set(DirectX_D3D11_INCLUDE_DIR ${DirectX_D3D11_INCLUDE_DIR})
set(DirectX_D3D11_LIBRARIES ${DirectX_D3D11_LIBRARIES}
${DirectX_D3D11_LIBRARY}
${DirectX_D3DX11_LIBRARY}
${DirectX_DXGI_LIBRARY}
${DirectX_DXERR_LIBRARY}
${DirectX_DXGUID_LIBRARY}
${DirectX_D3DCOMPILER_LIBRARY}
)
endif ()
mark_as_advanced(DirectX_D3D11_INCLUDE_DIR DirectX_D3D11_LIBRARY DirectX_D3DX11_LIBRARY)
endif ()
endif(WIN32)

View File

@@ -0,0 +1,146 @@
#-------------------------------------------------------------------
# This file is part of the CMake build system for OGRE
# (Object-oriented Graphics Rendering Engine)
# For the latest info, see http://www.ogre3d.org/
#
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
##################################################################
# Provides some common functionality for the FindPackage modules
##################################################################
# Begin processing of package
macro(findpkg_begin PREFIX)
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Looking for ${PREFIX}...")
endif ()
endmacro(findpkg_begin)
# Display a status message unless FIND_QUIETLY is set
macro(pkg_message PREFIX)
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS ${ARGN})
endif ()
endmacro(pkg_message)
# Get environment variable, define it as ENV_$var and make sure backslashes are converted to forward slashes
macro(getenv_path VAR)
set(ENV_${VAR} $ENV{${VAR}})
# replace won't work if var is blank
if (ENV_${VAR})
string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} )
endif ()
endmacro(getenv_path)
# Construct search paths for includes and libraries from a PREFIX_PATH
macro(create_search_paths PREFIX)
foreach(dir ${${PREFIX}_PREFIX_PATH})
set(${PREFIX}_INC_SEARCH_PATH ${${PREFIX}_INC_SEARCH_PATH}
${dir}/include ${dir}/include/${PREFIX} ${dir}/Headers)
set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH}
${dir}/lib ${dir}/lib/${PREFIX} ${dir}/Libs)
endforeach(dir)
set(${PREFIX}_FRAMEWORK_SEARCH_PATH ${${PREFIX}_PREFIX_PATH})
endmacro(create_search_paths)
# clear cache variables if a certain variable changed
macro(clear_if_changed TESTVAR)
# test against internal check variable
if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}")
message(STATUS "${TESTVAR} changed.")
foreach(var ${ARGN})
set(${var} "NOTFOUND" CACHE STRING "x" FORCE)
endforeach(var)
endif ()
set(${TESTVAR}_INT_CHECK "${${TESTVAR}}" CACHE INTERNAL "x" FORCE)
endmacro(clear_if_changed)
# Try to get some hints from pkg-config, if available
macro(use_pkgconfig PREFIX PKGNAME)
# Android does not support PKG_CONFIG so we disable it
IF ( NOT ANDROID )
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(${PREFIX} ${PKGNAME})
endif ()
ENDIF ( NOT ANDROID )
endmacro (use_pkgconfig)
# Couple a set of release AND debug libraries (or frameworks)
macro(make_library_set PREFIX)
if (${PREFIX}_FWK)
set(${PREFIX} ${${PREFIX}_FWK})
elseif (${PREFIX}_REL AND ${PREFIX}_DBG)
set(${PREFIX} optimized ${${PREFIX}_REL} debug ${${PREFIX}_DBG})
elseif (${PREFIX}_REL)
set(${PREFIX} ${${PREFIX}_REL})
elseif (${PREFIX}_DBG)
set(${PREFIX} ${${PREFIX}_DBG})
endif ()
endmacro(make_library_set)
# Generate debug names from given release names
macro(get_debug_names PREFIX)
foreach(i ${${PREFIX}})
set(${PREFIX}_DBG ${${PREFIX}_DBG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i})
endforeach(i)
endmacro(get_debug_names)
# Add the parent dir from DIR to VAR
macro(add_parent_dir VAR DIR)
get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE)
set(${VAR} ${${VAR}} ${${DIR}_TEMP})
endmacro(add_parent_dir)
# Do the final processing for the package find.
macro(findpkg_finish PREFIX)
# skip if already processed during this run
if (NOT ${PREFIX}_FOUND)
if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY)
set(${PREFIX}_FOUND TRUE)
set(${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR})
set(${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY})
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Found ${PREFIX}: ${${PREFIX}_LIBRARIES}")
endif ()
else ()
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Could not locate ${PREFIX}")
endif ()
if (${PREFIX}_FIND_REQUIRED)
message(FATAL_ERROR "Required library ${PREFIX} not found! Install the library (including dev packages) and try again. If the library is already installed, set the missing variables manually in cmake.")
endif ()
endif ()
mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_REL ${PREFIX}_LIBRARY_DBG ${PREFIX}_LIBRARY_FWK)
endif ()
endmacro(findpkg_finish)
# Slightly customised framework finder
MACRO(findpkg_framework fwk)
IF(APPLE)
SET(${fwk}_FRAMEWORK_PATH
${${fwk}_FRAMEWORK_SEARCH_PATH}
${CMAKE_FRAMEWORK_PATH}
~/Library/Frameworks
/Library/Frameworks
/System/Library/Frameworks
/Network/Library/Frameworks
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/
)
FOREACH(dir ${${fwk}_FRAMEWORK_PATH})
SET(fwkpath ${dir}/${fwk}.framework)
IF(EXISTS ${fwkpath})
SET(${fwk}_FRAMEWORK_INCLUDES ${${fwk}_FRAMEWORK_INCLUDES}
${fwkpath}/Headers ${fwkpath}/PrivateHeaders)
if (NOT ${fwk}_LIBRARY_FWK)
SET(${fwk}_LIBRARY_FWK "-framework ${fwk}")
endif ()
ENDIF(EXISTS ${fwkpath})
ENDFOREACH(dir)
ENDIF(APPLE)
ENDMACRO(findpkg_framework)

View File

@@ -0,0 +1,20 @@
# Try to find real time libraries
# Once done, this will define
#
# RT_FOUND - system has rt library
# RT_LIBRARIES - rt libraries directory
#
# Source: https://gitlab.cern.ch/dss/eos/commit/44070e575faaa46bd998708ef03eedb381506ff0
#
if(RT_LIBRARIES)
set(RT_FIND_QUIETLY TRUE)
endif(RT_LIBRARIES)
find_library(RT_LIBRARY rt)
set(RT_LIBRARIES ${RT_LIBRARY})
# handle the QUIETLY and REQUIRED arguments and set
# RT_FOUND to TRUE if all listed variables are TRUE
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RT DEFAULT_MSG RT_LIBRARY)
mark_as_advanced(RT_LIBRARY)

View File

@@ -0,0 +1,48 @@
#-------------------------------------------------------------------
# This file is part of the CMake build system for OGRE
# (Object-oriented Graphics Rendering Engine)
# For the latest info, see http://www.ogre3d.org/
#
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
# - Try to find ZLIB
# Once done, this will define
#
# ZLIB_FOUND - system has ZLIB
# ZLIB_INCLUDE_DIRS - the ZLIB include directories
# ZLIB_LIBRARIES - link these to use ZLIB
include(FindPkgMacros)
findpkg_begin(ZLIB)
# Get path, convert backslashes as ${ENV_${var}}
getenv_path(ZLIB_HOME)
# construct search paths
set(ZLIB_PREFIX_PATH ${ZLIB_HOME} ${ENV_ZLIB_HOME})
create_search_paths(ZLIB)
# redo search if prefix path changed
clear_if_changed(ZLIB_PREFIX_PATH
ZLIB_LIBRARY_FWK
ZLIB_LIBRARY_REL
ZLIB_LIBRARY_DBG
ZLIB_INCLUDE_DIR
)
set(ZLIB_LIBRARY_NAMES z zlib zdll)
get_debug_names(ZLIB_LIBRARY_NAMES)
use_pkgconfig(ZLIB_PKGC zzip-zlib-config)
findpkg_framework(ZLIB)
find_path(ZLIB_INCLUDE_DIR NAMES zlib.h HINTS ${ZLIB_INC_SEARCH_PATH} ${ZLIB_PKGC_INCLUDE_DIRS})
find_library(ZLIB_LIBRARY_REL NAMES ${ZLIB_LIBRARY_NAMES} HINTS ${ZLIB_LIB_SEARCH_PATH} ${ZLIB_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
find_library(ZLIB_LIBRARY_DBG NAMES ${ZLIB_LIBRARY_NAMES_DBG} HINTS ${ZLIB_LIB_SEARCH_PATH} ${ZLIB_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
make_library_set(ZLIB_LIBRARY)
findpkg_finish(ZLIB)

View File

@@ -0,0 +1,113 @@
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ASSIMP_ARCHITECTURE "64")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(ASSIMP_ARCHITECTURE "32")
endif(CMAKE_SIZEOF_VOID_P EQUAL 8)
if(WIN32)
set(ASSIMP_ROOT_DIR CACHE PATH "ASSIMP root directory")
# Find path of each library
find_path(ASSIMP_INCLUDE_DIR
NAMES
assimp/anim.h
HINTS
${ASSIMP_ROOT_DIR}/include
)
if(MSVC12)
set(ASSIMP_MSVC_VERSION "vc120")
elseif(MSVC14)
set(ASSIMP_MSVC_VERSION "vc140")
endif(MSVC12)
if(MSVC12 OR MSVC14)
find_path(ASSIMP_LIBRARY_DIR
NAMES
assimp-${ASSIMP_MSVC_VERSION}-mt.lib
HINTS
${ASSIMP_ROOT_DIR}/lib${ASSIMP_ARCHITECTURE}
)
find_library(ASSIMP_LIBRARY_RELEASE assimp-${ASSIMP_MSVC_VERSION}-mt.lib PATHS ${ASSIMP_LIBRARY_DIR})
find_library(ASSIMP_LIBRARY_DEBUG assimp-${ASSIMP_MSVC_VERSION}-mtd.lib PATHS ${ASSIMP_LIBRARY_DIR})
set(ASSIMP_LIBRARY
optimized ${ASSIMP_LIBRARY_RELEASE}
debug ${ASSIMP_LIBRARY_DEBUG}
)
set(ASSIMP_LIBRARIES "ASSIMP_LIBRARY_RELEASE" "ASSIMP_LIBRARY_DEBUG")
FUNCTION(ASSIMP_COPY_BINARIES TargetDirectory)
ADD_CUSTOM_TARGET(AssimpCopyBinaries
COMMAND ${CMAKE_COMMAND} -E copy ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE}/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${TargetDirectory}/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll
COMMAND ${CMAKE_COMMAND} -E copy ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE}/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${TargetDirectory}/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll
COMMENT "Copying Assimp binaries to '${TargetDirectory}'"
VERBATIM)
ENDFUNCTION(ASSIMP_COPY_BINARIES)
if (NOT TARGET ASSIMP)
set(INCLUDE_DIRS ${ASSIMP_ROOT_DIR}/include)
find_library(ASSIMP_LIB_DEBUG
NAMES assimp-${ASSIMP_MSVC_VERSION}-mtd.lib
PATHS ${ASSIMP_LIBRARY_DIR})
find_file(ASSIMP_DLL_DEBUG
NAMES assimp-${ASSIMP_MSVC_VERSION}-mtd.dll
PATHS ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE})
find_library(ASSIMP_LIB_RELEASE
NAMES assimp-${ASSIMP_MSVC_VERSION}-mt.lib
PATHS ${ASSIMP_LIBRARY_DIR})
find_file(ASSIMP_DLL_RELEASE
NAMES assimp-${ASSIMP_MSVC_VERSION}-mt.dll
PATHS ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE})
add_library(ASSIMP SHARED IMPORTED)
set_target_properties(ASSIMP PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${INCLUDE_DIRS}"
IMPORTED_IMPLIB_DEBUG ${ASSIMP_LIB_DEBUG}
IMPORTED_IMPLIB_RELEASE ${ASSIMP_LIB_RELEASE}
IMPORTED_LOCATION_DEBUG ${ASSIMP_DLL_DEBUG}
IMPORTED_LOCATION_RELEASE ${ASSIMP_DLL_RELEASE}
)
endif()
endif()
else(WIN32)
find_path(
assimp_INCLUDE_DIRS
NAMES assimp/postprocess.h assimp/scene.h assimp/version.h assimp/config.h assimp/cimport.h
PATHS /usr/local/include
PATHS /usr/include/
)
find_library(
assimp_LIBRARIES
NAMES assimp
PATHS /usr/local/lib/
PATHS /usr/lib64/
PATHS /usr/lib/
)
if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
SET(assimp_FOUND TRUE)
ENDIF (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
if (assimp_FOUND)
if (NOT assimp_FIND_QUIETLY)
message(STATUS "Found asset importer library: ${assimp_LIBRARIES}")
endif (NOT assimp_FIND_QUIETLY)
else (assimp_FOUND)
if (assimp_FIND_REQUIRED)
message(FATAL_ERROR "Could not find asset importer library")
endif (assimp_FIND_REQUIRED)
endif (assimp_FOUND)
endif(WIN32)

View File

@@ -0,0 +1,510 @@
# Copyright (c) 2013-2019, Ruslan Baratov
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
include(CMakeParseArguments) # cmake_parse_arguments
option(HUNTER_STATUS_PRINT "Print working status" ON)
option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
set(HUNTER_ERROR_PAGE "https://hunter.readthedocs.io/en/latest/reference/errors")
function(hunter_gate_status_print)
if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
message(STATUS "[hunter] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_status_debug)
if(HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
string(TIMESTAMP timestamp)
message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_error_page error_page)
message("------------------------------ ERROR ------------------------------")
message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
message("-------------------------------------------------------------------")
message("")
message(FATAL_ERROR "")
endfunction()
function(hunter_gate_internal_error)
message("")
foreach(print_message ${ARGV})
message("[hunter ** INTERNAL **] ${print_message}")
endforeach()
message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("error.internal")
endfunction()
function(hunter_gate_fatal_error)
cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
if("${hunter_ERROR_PAGE}" STREQUAL "")
hunter_gate_internal_error("Expected ERROR_PAGE")
endif()
message("")
foreach(x ${hunter_UNPARSED_ARGUMENTS})
message("[hunter ** FATAL ERROR **] ${x}")
endforeach()
message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("${hunter_ERROR_PAGE}")
endfunction()
function(hunter_gate_user_error)
hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
endfunction()
function(hunter_gate_self root version sha1 result)
string(COMPARE EQUAL "${root}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("root is empty")
endif()
string(COMPARE EQUAL "${version}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("version is empty")
endif()
string(COMPARE EQUAL "${sha1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("sha1 is empty")
endif()
string(SUBSTRING "${sha1}" 0 7 archive_id)
if(EXISTS "${root}/cmake/Hunter")
set(hunter_self "${root}")
else()
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
endif()
set("${result}" "${hunter_self}" PARENT_SCOPE)
endfunction()
# Set HUNTER_GATE_ROOT cmake variable to suitable value.
function(hunter_gate_detect_root)
# Check CMake variable
string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
return()
endif()
# Check environment variable
string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
return()
endif()
# Check HOME environment variable
string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
return()
endif()
# Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
if(WIN32)
string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using SYSTEMDRIVE environment variable"
)
return()
endif()
string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using USERPROFILE environment variable"
)
return()
endif()
endif()
hunter_gate_fatal_error(
"Can't detect HUNTER_ROOT"
ERROR_PAGE "error.detect.hunter.root"
)
endfunction()
function(hunter_gate_download dir)
string(
COMPARE
NOTEQUAL
"$ENV{HUNTER_DISABLE_AUTOINSTALL}"
""
disable_autoinstall
)
if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
hunter_gate_fatal_error(
"Hunter not found in '${dir}'"
"Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
"Settings:"
" HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
" HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
ERROR_PAGE "error.run.install"
)
endif()
string(COMPARE EQUAL "${dir}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("Empty 'dir' argument")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_URL empty")
endif()
set(done_location "${dir}/DONE")
set(sha1_location "${dir}/SHA1")
set(build_dir "${dir}/Build")
set(cmakelists "${dir}/CMakeLists.txt")
hunter_gate_status_debug("Locking directory: ${dir}")
file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
hunter_gate_status_debug("Lock done")
if(EXISTS "${done_location}")
# while waiting for lock other instance can do all the job
hunter_gate_status_debug("File '${done_location}' found, skip install")
return()
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
# Disabling languages speeds up a little bit, reduces noise in the output
# and avoids path too long windows error
file(
WRITE
"${cmakelists}"
"cmake_minimum_required(VERSION 3.2)\n"
"project(HunterDownload LANGUAGES NONE)\n"
"include(ExternalProject)\n"
"ExternalProject_Add(\n"
" Hunter\n"
" URL\n"
" \"${HUNTER_GATE_URL}\"\n"
" URL_HASH\n"
" SHA1=${HUNTER_GATE_SHA1}\n"
" DOWNLOAD_DIR\n"
" \"${dir}\"\n"
" TLS_VERIFY\n"
" ${HUNTER_TLS_VERIFY}\n"
" SOURCE_DIR\n"
" \"${dir}/Unpacked\"\n"
" CONFIGURE_COMMAND\n"
" \"\"\n"
" BUILD_COMMAND\n"
" \"\"\n"
" INSTALL_COMMAND\n"
" \"\"\n"
")\n"
)
if(HUNTER_STATUS_DEBUG)
set(logging_params "")
else()
set(logging_params OUTPUT_QUIET)
endif()
hunter_gate_status_debug("Run generate")
# Need to add toolchain file too.
# Otherwise on Visual Studio + MDD this will fail with error:
# "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
else()
# 'toolchain_arg' can't be empty
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
endif()
string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
if(no_make)
set(make_arg "")
else()
# Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
endif()
execute_process(
COMMAND
"${CMAKE_COMMAND}"
"-H${dir}"
"-B${build_dir}"
"-G${CMAKE_GENERATOR}"
"${toolchain_arg}"
${make_arg}
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error(
"Configure project failed."
"To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
"In directory ${dir}"
)
endif()
hunter_gate_status_print(
"Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
" ${HUNTER_GATE_URL}"
" -> ${dir}"
)
execute_process(
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error("Build project failed")
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
file(WRITE "${done_location}" "DONE")
hunter_gate_status_debug("Finished")
endfunction()
# Must be a macro so master file 'cmake/Hunter' can
# apply all variables easily just by 'include' command
# (otherwise PARENT_SCOPE magic needed)
macro(HunterGate)
if(HUNTER_GATE_DONE)
# variable HUNTER_GATE_DONE set explicitly for external project
# (see `hunter_download`)
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
# First HunterGate command will init Hunter, others will be ignored
get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
if(NOT HUNTER_ENABLED)
# Empty function to avoid error "unknown function"
function(hunter_add_package)
endfunction()
set(
_hunter_gate_disabled_mode_dir
"${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
)
if(EXISTS "${_hunter_gate_disabled_mode_dir}")
hunter_gate_status_debug(
"Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
)
list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
endif()
elseif(_hunter_gate_done)
hunter_gate_status_debug("Secondary HunterGate (use old settings)")
hunter_gate_self(
"${HUNTER_CACHED_ROOT}"
"${HUNTER_VERSION}"
"${HUNTER_SHA1}"
_hunter_self
)
include("${_hunter_self}/cmake/Hunter")
else()
set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
if(_have_project_name)
hunter_gate_fatal_error(
"Please set HunterGate *before* 'project' command. "
"Detected project: ${PROJECT_NAME}"
ERROR_PAGE "error.huntergate.before.project"
)
endif()
cmake_parse_arguments(
HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
)
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
string(
COMPARE
NOTEQUAL
"${HUNTER_GATE_UNPARSED_ARGUMENTS}"
""
_have_unparsed
)
string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
if(_have_unparsed)
hunter_gate_user_error(
"HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
)
endif()
if(_empty_sha1)
hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
endif()
if(_empty_url)
hunter_gate_user_error("URL suboption of HunterGate is mandatory")
endif()
if(_have_global)
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
endif()
endif()
if(HUNTER_GATE_LOCAL)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
endif()
endif()
if(_have_filepath)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
endif()
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
endif()
endif()
hunter_gate_detect_root() # set HUNTER_GATE_ROOT
# Beautify path, fix probable problems with windows path slashes
get_filename_component(
HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
)
hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
if(NOT _contain_spaces EQUAL -1)
hunter_gate_fatal_error(
"HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
"Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
"(Use at your own risk!)"
ERROR_PAGE "error.spaces.in.hunter.root"
)
endif()
endif()
string(
REGEX
MATCH
"[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
HUNTER_GATE_VERSION
"${HUNTER_GATE_URL}"
)
string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
if(_is_empty)
set(HUNTER_GATE_VERSION "unknown")
endif()
hunter_gate_self(
"${HUNTER_GATE_ROOT}"
"${HUNTER_GATE_VERSION}"
"${HUNTER_GATE_SHA1}"
_hunter_self
)
set(_master_location "${_hunter_self}/cmake/Hunter")
if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
# Hunter downloaded manually (e.g. by 'git clone')
set(_unused "xxxxxxxxxx")
set(HUNTER_GATE_SHA1 "${_unused}")
set(HUNTER_GATE_VERSION "${_unused}")
else()
get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
set(_done_location "${_archive_id_location}/DONE")
set(_sha1_location "${_archive_id_location}/SHA1")
# Check Hunter already downloaded by HunterGate
if(NOT EXISTS "${_done_location}")
hunter_gate_download("${_archive_id_location}")
endif()
if(NOT EXISTS "${_done_location}")
hunter_gate_internal_error("hunter_gate_download failed")
endif()
if(NOT EXISTS "${_sha1_location}")
hunter_gate_internal_error("${_sha1_location} not found")
endif()
file(READ "${_sha1_location}" _sha1_value)
string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
if(NOT _is_equal)
hunter_gate_internal_error(
"Short SHA1 collision:"
" ${_sha1_value} (from ${_sha1_location})"
" ${HUNTER_GATE_SHA1} (HunterGate)"
)
endif()
if(NOT EXISTS "${_master_location}")
hunter_gate_user_error(
"Master file not found:"
" ${_master_location}"
"try to update Hunter/HunterGate"
)
endif()
endif()
include("${_master_location}")
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
endmacro()

View File

@@ -0,0 +1,16 @@
# this one sets internal to crosscompile (in theory)
SET(CMAKE_SYSTEM_NAME Windows)
# the minimalistic settings
SET(CMAKE_C_COMPILER "/usr/bin/x86_64-w64-mingw32-gcc")
SET(CMAKE_CXX_COMPILER "/usr/bin/x86_64-w64-mingw32-g++")
SET(CMAKE_RC_COMPILER "/usr/bin/x86_64-w64-mingw32-windres")
# where is the target (so called staging) environment
SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
# search for programs in the build host directories (default BOTH)
#SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@@ -0,0 +1,25 @@
MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar)
IF(MSVC)
GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE)
SET(PrecompiledBinary "${CMAKE_CFG_INTDIR}/${PrecompiledBasename}.pch")
SET(Sources ${${SourcesVar}})
SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource}
PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
OBJECT_OUTPUTS "${PrecompiledBinary}")
# Do not consider .c files
foreach(fname ${Sources})
GET_FILENAME_COMPONENT(fext ${fname} EXT)
if(fext STREQUAL ".cpp")
SET_SOURCE_FILES_PROPERTIES(${fname}
PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledBinary}\" /FI\"${PrecompiledBinary}\" /Fp\"${PrecompiledBinary}\""
OBJECT_DEPENDS "${PrecompiledBinary}")
endif(fext STREQUAL ".cpp")
endforeach(fname)
ENDIF(MSVC)
# Add precompiled header to SourcesVar
LIST(APPEND ${SourcesVar} ${PrecompiledSource})
ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER)

View File

@@ -0,0 +1,19 @@
@PACKAGE_INIT@
find_package(RapidJSON CONFIG REQUIRED)
find_package(ZLIB CONFIG REQUIRED)
find_package(utf8cpp CONFIG REQUIRED)
find_package(minizip CONFIG REQUIRED)
find_package(openddlparser CONFIG REQUIRED)
find_package(poly2tri CONFIG REQUIRED)
find_package(polyclipping CONFIG REQUIRED)
find_package(zip CONFIG REQUIRED)
find_package(pugixml CONFIG REQUIRED)
find_package(stb CONFIG REQUIRED)
if(@ASSIMP_BUILD_DRACO@)
find_package(draco CONFIG REQUIRED)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
check_required_components("@PROJECT_NAME@")

View File

@@ -0,0 +1,9 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
set(ASSIMP_ROOT_DIR ${PACKAGE_PREFIX_DIR})
set(ASSIMP_LIBRARIES assimp::assimp)
set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@)
get_property(ASSIMP_INCLUDE_DIRS TARGET assimp::assimp PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
set(ASSIMP_LIBRARY_DIRS "")

View File

@@ -0,0 +1,17 @@
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
STRING(REGEX REPLACE "\n" ";" files "${files}")
FOREACH(file ${files})
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ENDFOREACH(file)

View File

@@ -0,0 +1,814 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/** @file Implementation of the 3ds importer class */
#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
// internal headers
#include "3DSLoader.h"
#include "Common/TargetAnimation.h"
#include <assimp/StringComparison.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <cctype>
#include <memory>
namespace Assimp {
static constexpr unsigned int NotSet = 0xcdcdcdcd;
using namespace D3DS;
// ------------------------------------------------------------------------------------------------
// Setup final material indices, generate a default material if necessary
void Discreet3DSImporter::ReplaceDefaultMaterial() {
// Try to find an existing material that matches the
// typical default material setting:
// - no textures
// - diffuse color (in grey!)
// NOTE: This is here to work-around the fact that some
// exporters are writing a default material, too.
unsigned int idx(NotSet);
for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) {
auto s = mScene->mMaterials[i].mName;
for (char &it : s) {
it = static_cast<char>(::tolower(static_cast<unsigned char>(it)));
}
if (std::string::npos == s.find("default")) {
continue;
}
if (mScene->mMaterials[i].mDiffuse.r !=
mScene->mMaterials[i].mDiffuse.g ||
mScene->mMaterials[i].mDiffuse.r !=
mScene->mMaterials[i].mDiffuse.b) continue;
if (ContainsTextures(i)) {
continue;
}
idx = i;
}
if (NotSet == idx) {
idx = static_cast<unsigned int>(mScene->mMaterials.size());
}
// now iterate through all meshes and through all faces and
// find all faces that are using the default material
unsigned int cnt = 0;
for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
for (auto a = i->mFaceMaterials.begin(); a != i->mFaceMaterials.end(); ++a) {
// NOTE: The additional check seems to be necessary,
// some exporters seem to generate invalid data here
if (NotSet == *a) {
*a = idx;
++cnt;
} else if ((*a) >= mScene->mMaterials.size()) {
*a = idx;
ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material");
++cnt;
}
}
}
if (cnt && idx == mScene->mMaterials.size()) {
// We need to create our own default material
Material sMat("%%%DEFAULT");
sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f);
mScene->mMaterials.push_back(sMat);
ASSIMP_LOG_INFO("3DS: Generating default material");
}
}
// ------------------------------------------------------------------------------------------------
// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached
void Discreet3DSImporter::CheckIndices(Mesh &sMesh) {
for (auto i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) {
// check whether all indices are in range
for (unsigned int a = 0; a < 3; ++a) {
if ((*i).mIndices[a] >= sMesh.mPositions.size()) {
ASSIMP_LOG_WARN("3DS: Vertex index overflow)");
(*i).mIndices[a] = static_cast<uint32_t>(sMesh.mPositions.size() - 1);
}
if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) {
ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)");
(*i).mIndices[a] = static_cast<uint32_t>(sMesh.mTexCoords.size() - 1);
}
}
}
}
// ------------------------------------------------------------------------------------------------
// Generate out unique verbose format representation
void Discreet3DSImporter::MakeUnique(Mesh &sMesh) {
// TODO: really necessary? I don't think. Just a waste of memory and time
// to do it now in a separate buffer.
// Allocate output storage
std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3);
std::vector<aiVector3D> vNew2;
if (sMesh.mTexCoords.size())
vNew2.resize(sMesh.mFaces.size() * 3);
for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) {
Face &face = sMesh.mFaces[i];
// Positions
for (unsigned int a = 0; a < 3; ++a, ++base) {
vNew[base] = sMesh.mPositions[face.mIndices[a]];
if (sMesh.mTexCoords.size())
vNew2[base] = sMesh.mTexCoords[face.mIndices[a]];
face.mIndices[a] = base;
}
}
sMesh.mPositions = vNew;
sMesh.mTexCoords = vNew2;
}
// ------------------------------------------------------------------------------------------------
// Convert a 3DS texture to texture keys in an aiMaterial
void CopyTexture(aiMaterial &mat, Texture &texture, aiTextureType type) {
// Setup the texture name
aiString tex(texture.mMapName);
mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0));
// Setup the texture blend factor
if (is_not_qnan(texture.mTextureBlend))
mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0));
// Setup the texture mapping mode
auto mapMode = static_cast<int>(texture.mMapMode);
mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0));
mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0));
// Mirroring - double the scaling values
// FIXME: this is not really correct ...
if (texture.mMapMode == aiTextureMapMode_Mirror) {
texture.mScaleU *= 2.0;
texture.mScaleV *= 2.0;
texture.mOffsetU /= 2.0;
texture.mOffsetV /= 2.0;
}
// Setup texture UV transformations
mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0));
}
// ------------------------------------------------------------------------------------------------
// Convert a 3DS material to an aiMaterial
void Discreet3DSImporter::ConvertMaterial(Material &oldMat, aiMaterial &mat) {
// NOTE: Pass the background image to the viewer by bypassing the
// material system. This is an evil hack, never do it again!
if (mBackgroundImage.empty() && bHasBG) {
aiString tex(mBackgroundImage);
mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE);
// Be sure this is only done for the first material
mBackgroundImage = std::string();
}
// At first add the base ambient color of the scene to the material
oldMat.mAmbient.r += mClrAmbient.r;
oldMat.mAmbient.g += mClrAmbient.g;
oldMat.mAmbient.b += mClrAmbient.b;
aiString name(oldMat.mName);
mat.AddProperty(&name, AI_MATKEY_NAME);
// Material colors
mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
// Phong shininess and shininess strength
if (Discreet3DS::Phong == oldMat.mShading || Discreet3DS::Metal == oldMat.mShading) {
if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) {
oldMat.mShading = Discreet3DS::Gouraud;
} else {
mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
}
}
// Opacity
mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY);
// Bump height scaling
mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING);
// Two sided rendering?
if (oldMat.mTwoSided) {
int i = 1;
mat.AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED);
}
// Shading mode
aiShadingMode eShading = aiShadingMode_NoShading;
switch (oldMat.mShading) {
case Discreet3DS::Flat:
eShading = aiShadingMode_Flat;
break;
// I don't know what "Wire" shading should be,
// assume it is simple lambertian diffuse shading
case Discreet3DS::Wire: {
// Set the wireframe flag
unsigned int iWire = 1;
mat.AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME);
}
[[fallthrough]];
case Discreet3DS::Gouraud:
eShading = aiShadingMode_Gouraud;
break;
// assume cook-torrance shading for metals.
case Discreet3DS::Phong:
eShading = aiShadingMode_Phong;
break;
case Discreet3DS::Metal:
eShading = aiShadingMode_CookTorrance;
break;
// FIX to workaround a warning with GCC 4 who complained
// about a missing case Blinn: here - Blinn isn't a valid
// value in the 3DS Loader, it is just needed for ASE
case Discreet3DS::Blinn:
eShading = aiShadingMode_Blinn;
break;
}
const int eShading_ = eShading;
mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL);
// DIFFUSE texture
if (oldMat.sTexDiffuse.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE);
// SPECULAR texture
if (oldMat.sTexSpecular.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR);
// OPACITY texture
if (oldMat.sTexOpacity.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY);
// EMISSIVE texture
if (oldMat.sTexEmissive.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE);
// BUMP texture
if (oldMat.sTexBump.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT);
// SHININESS texture
if (oldMat.sTexShininess.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS);
// REFLECTION texture
if (oldMat.sTexReflective.mMapName.length() > 0)
CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION);
// Store the name of the material itself, too
if (oldMat.mName.length()) {
aiString tex;
tex.Set(oldMat.mName);
mat.AddProperty(&tex, AI_MATKEY_NAME);
}
}
// ------------------------------------------------------------------------------------------------
// Split meshes by their materials and generate output aiMesh'es
void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) {
std::vector<aiMesh *> avOutMeshes;
avOutMeshes.reserve(mScene->mMeshes.size() * 2);
unsigned int iFaceCnt = 0, num = 0;
aiString name;
// we need to split all meshes by their materials
for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]);
name.length = ASSIMP_itoa10(name.data, num);
++num;
unsigned int iNum = 0;
for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin();
a != (*i).mFaceMaterials.end(); ++a, ++iNum) {
aiSplit[*a].push_back(iNum);
}
// now generate submeshes
for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) {
if (aiSplit[p].empty()) {
continue;
}
auto *meshOut = new aiMesh();
meshOut->mName = name;
meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
// be sure to setup the correct material index
meshOut->mMaterialIndex = p;
// use the color data as temporary storage
meshOut->mColors[0] = (aiColor4D *)(&*i);
avOutMeshes.push_back(meshOut);
// convert vertices
meshOut->mNumFaces = static_cast<unsigned int>(aiSplit[p].size());
meshOut->mNumVertices = meshOut->mNumFaces * 3;
// allocate enough storage for faces
meshOut->mFaces = new aiFace[meshOut->mNumFaces];
iFaceCnt += meshOut->mNumFaces;
meshOut->mVertices = new aiVector3D[meshOut->mNumVertices];
meshOut->mNormals = new aiVector3D[meshOut->mNumVertices];
if ((*i).mTexCoords.size()) {
meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices];
}
for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) {
unsigned int index = aiSplit[p][q];
aiFace &face = meshOut->mFaces[q];
face.mIndices = new unsigned int[3];
face.mNumIndices = 3;
for (unsigned int a = 0; a < 3; ++a, ++base) {
unsigned int idx = (*i).mFaces[index].mIndices[a];
meshOut->mVertices[base] = (*i).mPositions[idx];
meshOut->mNormals[base] = (*i).mNormals[idx];
if ((*i).mTexCoords.size())
meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx];
face.mIndices[a] = base;
}
}
}
}
// Copy them to the output array
pcOut->mNumMeshes = (unsigned int)avOutMeshes.size();
pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes]();
for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
pcOut->mMeshes[a] = avOutMeshes[a];
}
// We should have at least one face here
if (!iFaceCnt) {
throw DeadlyImportError("No faces loaded. The mesh is empty");
}
}
// ------------------------------------------------------------------------------------------------
// Add a node to the scenegraph and setup its final transformation
void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) {
std::vector<unsigned int> iArray;
iArray.reserve(3);
aiMatrix4x4 abs;
// Find all meshes with the same name as the node
for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) {
const auto *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0];
ai_assert(nullptr != pcMesh);
if (pcIn->mName == pcMesh->mName)
iArray.push_back(a);
}
if (!iArray.empty()) {
// The matrix should be identical for all meshes with the
// same name. It HAS to be identical for all meshes .....
auto *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]);
// Compute the inverse of the transformation matrix to move the
// vertices back to their relative and local space
aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
mInv.Inverse();
mInvTransposed.Transpose();
aiVector3D pivot = pcIn->vPivot;
pcOut->mNumMeshes = static_cast<unsigned int>(iArray.size());
pcOut->mMeshes = new unsigned int[iArray.size()];
for (unsigned int i = 0; i < iArray.size(); ++i) {
const unsigned int iIndex = iArray[i];
aiMesh *const mesh = pcSOut->mMeshes[iIndex];
if (mesh->mColors[1] == nullptr) {
// Transform the vertices back into their local space
// fixme: consider computing normals after this, so we don't need to transform them
const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices;
aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
*pvCurrent = mInv * (*pvCurrent);
*t2 = mInvTransposed * (*t2);
}
// Handle negative transformation matrix determinant -> invert vertex x
if (imesh->mMat.Determinant() < 0.0f) {
// we *must* have normals
for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
pvCurrent->x *= -1.f;
t2->x *= -1.f;
}
ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis");
}
// Handle pivot point
if (pivot.x || pivot.y || pivot.z) {
for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) {
*pvCurrent -= pivot;
}
}
mesh->mColors[1] = (aiColor4D *)1;
} else
mesh->mColors[1] = (aiColor4D *)1;
// Setup the mesh index
pcOut->mMeshes[i] = iIndex;
}
}
// Setup the name of the node
// First instance keeps its name otherwise something might break, all others will be postfixed with their instance number
if (pcIn->mInstanceNumber > 1) {
char tmp[12] = {'\0'};
ASSIMP_itoa10(tmp, pcIn->mInstanceNumber);
std::string tempStr = pcIn->mName + "_inst_";
tempStr += tmp;
pcOut->mName.Set(tempStr);
} else
pcOut->mName.Set(pcIn->mName);
// Now build the transformation matrix of the node
// ROTATION
if (pcIn->aRotationKeys.size()) {
// FIX to get to Assimp's quaternion conventions
for (auto it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) {
(*it).mValue.w *= -1.f;
}
pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix());
} else if (pcIn->aCameraRollKeys.size()) {
aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue),
pcOut->mTransformation);
}
// SCALING
aiMatrix4x4 &m = pcOut->mTransformation;
if (pcIn->aScalingKeys.size()) {
const aiVector3D &v = pcIn->aScalingKeys[0].mValue;
m.a1 *= v.x;
m.b1 *= v.x;
m.c1 *= v.x;
m.a2 *= v.y;
m.b2 *= v.y;
m.c2 *= v.y;
m.a3 *= v.z;
m.b3 *= v.z;
m.c3 *= v.z;
}
// TRANSLATION
if (pcIn->aPositionKeys.size()) {
const aiVector3D &v = pcIn->aPositionKeys[0].mValue;
m.a4 += v.x;
m.b4 += v.y;
m.c4 += v.z;
}
// Generate animation channels for the node
if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 ||
pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 ||
pcIn->aTargetPositionKeys.size() > 1) {
aiAnimation *anim = pcSOut->mAnimations[0];
ai_assert(nullptr != anim);
if (pcIn->aCameraRollKeys.size() > 1) {
ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ...");
// Camera roll keys - in fact they're just rotations
// around the camera's z axis. The angles are given
// in degrees (and they're clockwise).
pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) {
aiQuatKey &q = pcIn->aRotationKeys[i];
aiFloatKey &f = pcIn->aCameraRollKeys[i];
q.mTime = f.mTime;
// FIX to get to Assimp quaternion conventions
q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue));
}
}
#if 0
if (pcIn->aTargetPositionKeys.size() > 1)
{
ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ...");
// Camera or spot light - need to convert the separate
// target position channel to our representation
TargetAnimationHelper helper;
if (pcIn->aPositionKeys.empty())
{
// We can just pass zero here ...
helper.SetFixedMainAnimationChannel(aiVector3D());
}
else helper.SetMainAnimationChannel(&pcIn->aPositionKeys);
helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys);
// Do the conversion
std::vector<aiVectorKey> distanceTrack;
helper.Process(&distanceTrack);
// Now add a new node as child, name it <ourName>.Target
// and assign the distance track to it. This is that the
// information where the target is and how it moves is
// not lost
D3DS::Node* nd = new D3DS::Node();
pcIn->push_back(nd);
nd->mName = pcIn->mName + ".Target";
aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
nda->mNodeName.Set(nd->mName);
nda->mNumPositionKeys = (unsigned int)distanceTrack.size();
nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
::memcpy(nda->mPositionKeys,&distanceTrack[0],
sizeof(aiVectorKey)*nda->mNumPositionKeys);
}
#endif
// Cameras or lights define their transformation in their parent node and in the
// corresponding light or camera chunks. However, we read and process the latter
// to be able to return valid cameras/lights even if no scenegraph is given.
for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) {
if (pcSOut->mCameras[n]->mName == pcOut->mName) {
pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f);
}
}
for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) {
if (pcSOut->mLights[n]->mName == pcOut->mName) {
pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f);
}
}
// Allocate a new node anim and setup its name
auto *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
nda->mNodeName.Set(pcIn->mName);
// POSITION keys
if (!pcIn->aPositionKeys.empty()) {
nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0],
sizeof(aiVectorKey) * nda->mNumPositionKeys);
}
// ROTATION keys
if (!pcIn->aRotationKeys.empty()) {
nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
// Rotations are quaternion offsets
aiQuaternion abs1;
for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) {
const aiQuatKey &q = pcIn->aRotationKeys[n];
abs1 = (n ? abs1 * q.mValue : q.mValue);
nda->mRotationKeys[n].mTime = q.mTime;
nda->mRotationKeys[n].mValue = abs1.Normalize();
}
}
// SCALING keys
if (!pcIn->aScalingKeys.empty()) {
nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0],
sizeof(aiVectorKey) * nda->mNumScalingKeys);
}
}
// Allocate storage for children
const auto size = static_cast<unsigned int>(pcIn->mChildren.size());
pcOut->mNumChildren = size;
if (size == 0) {
return;
}
pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
// Recursively process all children
for (unsigned int i = 0; i < size; ++i) {
pcOut->mChildren[i] = new aiNode();
pcOut->mChildren[i]->mParent = pcOut;
AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs);
}
}
// ------------------------------------------------------------------------------------------------
// Find out how many node animation channels we'll have finally
void CountTracks(D3DS::Node *node, unsigned int &cnt) {
//////////////////////////////////////////////////////////////////////////////
// We will never generate more than one channel for a node, so
// this is rather easy here.
if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 ||
node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 ||
node->aTargetPositionKeys.size() > 1) {
++cnt;
// account for the additional channel for the camera/spotlight target position
if (node->aTargetPositionKeys.size() > 1) ++cnt;
}
// Recursively process all children
for (unsigned int i = 0; i < node->mChildren.size(); ++i)
CountTracks(node->mChildren[i], cnt);
}
// ------------------------------------------------------------------------------------------------
// Generate the output node graph
void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) {
pcOut->mRootNode = new aiNode();
if (mRootNode->mChildren.empty()) {
//////////////////////////////////////////////////////////////////////////////
// It seems the file is so messed up that it has not even a hierarchy.
// generate a flat hiearachy which looks like this:
//
// ROOT_NODE
// |
// ----------------------------------------
// | | | | |
// MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 ....
//
ASSIMP_LOG_WARN("No hierarchy information has been found in the file. ");
pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes +
static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size());
pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren];
pcOut->mRootNode->mName.Set("<3DSDummyRoot>");
// Build dummy nodes for all meshes
unsigned int a = 0;
for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) {
pcOut->mRootNode->mChildren[a] = new aiNode();
auto *pcNode = pcOut->mRootNode->mChildren[a];
pcNode->mParent = pcOut->mRootNode;
pcNode->mMeshes = new unsigned int[1];
pcNode->mMeshes[0] = i;
pcNode->mNumMeshes = 1;
// Build a name for the node
pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "3DSMesh_%u", i);
}
// Build dummy nodes for all cameras
for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) {
auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
pcNode->mParent = pcOut->mRootNode;
// Build a name for the node
pcNode->mName = mScene->mCameras[i]->mName;
}
// Build dummy nodes for all lights
for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) {
auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
pcNode->mParent = pcOut->mRootNode;
// Build a name for the node
pcNode->mName = mScene->mLights[i]->mName;
}
} else {
// First of all: find out how many scaling, rotation and translation
// animation tracks we'll have afterwards
unsigned int numChannel = 0;
CountTracks(mRootNode, numChannel);
if (numChannel) {
// Allocate a primary animation channel
pcOut->mNumAnimations = 1;
pcOut->mAnimations = new aiAnimation *[1];
auto *anim = pcOut->mAnimations[0] = new aiAnimation();
anim->mName.Set("3DSMasterAnim");
// Allocate enough storage for all node animation channels,
// but don't set the mNumChannels member - we'll use it to
// index into the array
anim->mChannels = new aiNodeAnim *[numChannel];
}
aiMatrix4x4 m;
AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m);
}
// We used the first and second vertex color set to store some temporary values so we need to cleanup here
for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
pcOut->mMeshes[a]->mColors[0] = nullptr;
pcOut->mMeshes[a]->mColors[1] = nullptr;
}
pcOut->mRootNode->mTransformation = aiMatrix4x4(
1.f, 0.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
0.f, -1.f, 0.f, 0.f,
0.f, 0.f, 0.f, 1.f) *
pcOut->mRootNode->mTransformation;
// If the root node is unnamed name it "<3DSRoot>"
if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") ||
(pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) {
pcOut->mRootNode->mName.Set("<3DSRoot>");
}
}
// ------------------------------------------------------------------------------------------------
// Convert all meshes in the scene and generate the final output scene.
void Discreet3DSImporter::ConvertScene(aiScene *pcOut) {
// Allocate enough storage for all output materials
pcOut->mNumMaterials = static_cast<unsigned int>(mScene->mMaterials.size());
pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials];
// ... and convert the 3DS materials to aiMaterial's
for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) {
auto *pcNew = new aiMaterial();
ConvertMaterial(mScene->mMaterials[i], *pcNew);
pcOut->mMaterials[i] = pcNew;
}
// Generate the output mesh list
ConvertMeshes(pcOut);
// Now copy all light sources to the output scene
pcOut->mNumLights = static_cast<unsigned int>(mScene->mLights.size());
if (pcOut->mNumLights) {
pcOut->mLights = new aiLight *[pcOut->mNumLights];
memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights);
}
// Now copy all cameras to the output scene
pcOut->mNumCameras = static_cast<unsigned int>(mScene->mCameras.size());
if (pcOut->mNumCameras) {
pcOut->mCameras = new aiCamera *[pcOut->mNumCameras];
memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras);
}
}
} // namespace Assimp
#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER

View File

@@ -0,0 +1,576 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
#include "AssetLib/3DS/3DSExporter.h"
#include "AssetLib/3DS/3DSHelper.h"
#include "AssetLib/3DS/3DSLoader.h"
#include "PostProcessing/SplitLargeMeshes.h"
#include <assimp/SceneCombiner.h>
#include <assimp/StringComparison.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/Exporter.hpp>
#include <assimp/Exceptional.h>
#include <assimp/IOSystem.hpp>
#include <memory>
namespace Assimp {
using namespace D3DS;
namespace {
//////////////////////////////////////////////////////////////////////////////////////
// Scope utility to write a 3DS file chunk.
//
// Upon construction, the chunk header is written with the chunk type (flags)
// filled out, but the chunk size left empty. Upon destruction, the correct chunk
// size based on the then-position of the output stream cursor is filled in.
class ChunkWriter {
enum {
CHUNK_SIZE_NOT_SET = 0xdeadbeef,
SIZE_OFFSET = 2
};
public:
ChunkWriter(StreamWriterLE &writer, uint16_t chunk_type) :
mWriter(writer) {
mChunkStartPos = writer.GetCurrentPos();
writer.PutU2(chunk_type);
writer.PutU4((uint32_t)CHUNK_SIZE_NOT_SET);
}
~ChunkWriter() {
std::size_t head_pos = mWriter.GetCurrentPos();
ai_assert(head_pos > mChunkStartPos);
const std::size_t chunk_size = head_pos - mChunkStartPos;
mWriter.SetCurrentPos(mChunkStartPos + SIZE_OFFSET);
mWriter.PutU4(static_cast<uint32_t>(chunk_size));
mWriter.SetCurrentPos(head_pos);
}
private:
StreamWriterLE &mWriter;
std::size_t mChunkStartPos;
};
// Return an unique name for a given |mesh| attached to |node| that
// preserves the mesh's given name if it has one. |index| is the index
// of the mesh in |aiScene::mMeshes|.
std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) {
static constexpr char underscore = '_';
char postfix[10] = { 0 };
ASSIMP_itoa10(postfix, index);
std::string result = node.mName.C_Str();
if (mesh.mName.length > 0) {
result += underscore;
result += mesh.mName.C_Str();
}
return result + underscore + postfix;
}
// Return an unique name for a given |mat| with original position |index|
// in |aiScene::mMaterials|. The name preserves the original material
// name if possible.
std::string GetMaterialName(const aiMaterial &mat, unsigned int index) {
static const std::string underscore = "_";
char postfix[10] = { 0 };
ASSIMP_itoa10(postfix, index);
if (aiString mat_name; AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) {
return mat_name.C_Str() + underscore + postfix;
}
return "Material" + underscore + postfix;
}
// Collect world transformations for each node
void CollectTrafos(const aiNode *node, std::map<const aiNode *, aiMatrix4x4> &trafos) {
const aiMatrix4x4 &parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
trafos[node] = parent * node->mTransformation;
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
CollectTrafos(node->mChildren[i], trafos);
}
}
// Generate a flat list of the meshes (by index) assigned to each node
void CollectMeshes(const aiNode *node, std::multimap<const aiNode *, unsigned int> &meshes) {
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
meshes.insert(std::make_pair(node, node->mMeshes[i]));
}
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
CollectMeshes(node->mChildren[i], meshes);
}
}
} // namespace
// ------------------------------------------------------------------------------------------------
// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp
void ExportScene3DS(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
std::shared_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb"));
if (!outfile) {
throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile));
}
// TODO: This extra copy should be avoided and all of this made a preprocess
// requirement of the 3DS exporter.
//
// 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively.
// SplitLargeMeshes can do this, but it requires the correct limit to be set
// which is not possible with the current way of specifying preprocess steps
// in |Exporter::ExportFormatEntry|.
aiScene *scenecopy_tmp;
SceneCombiner::CopyScene(&scenecopy_tmp, pScene);
std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
SplitLargeMeshesProcess_Triangle tri_splitter;
tri_splitter.SetLimit(0xffff);
tri_splitter.Execute(scenecopy.get());
SplitLargeMeshesProcess_Vertex vert_splitter;
vert_splitter.SetLimit(0xffff);
vert_splitter.Execute(scenecopy.get());
// Invoke the actual exporter
Discreet3DSExporter exporter(outfile, scenecopy.get());
}
} // end of namespace Assimp
// ------------------------------------------------------------------------------------------------
Discreet3DSExporter::Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene *scene) :
scene(scene), writer(outfile) {
CollectTrafos(scene->mRootNode, trafos);
CollectMeshes(scene->mRootNode, meshes);
ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAIN);
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_OBJMESH);
WriteMaterials();
WriteMeshes();
{
ChunkWriter curChunk1(writer, Discreet3DS::CHUNK_MASTER_SCALE);
writer.PutF4(1.0f);
}
}
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_KEYFRAMER);
WriteHierarchy(*scene->mRootNode, -1, -1);
}
}
// ------------------------------------------------------------------------------------------------
int Discreet3DSExporter::WriteHierarchy(const aiNode &node, int seq, int sibling_level) {
// 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec
{
ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_TRACKINFO);
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
// Assimp node names are unique and distinct from all mesh-node
// names we generate; thus we can use them as-is
WriteString(node.mName);
// Two unknown int16 values - it is even unclear if 0 is a safe value
// but luckily importers do not know better either.
writer.PutI4(0);
int16_t hierarchy_pos = static_cast<int16_t>(seq);
if (sibling_level != -1) {
hierarchy_pos = (uint16_t)sibling_level;
}
// Write the hierarchy position
writer.PutI2(hierarchy_pos);
}
}
// TODO: write transformation chunks
++seq;
sibling_level = seq;
// Write all children
for (unsigned int i = 0; i < node.mNumChildren; ++i) {
seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level);
}
// Write all meshes as separate nodes to be able to reference the meshes by name
for (unsigned int i = 0; i < node.mNumMeshes; ++i) {
const bool first_child = node.mNumChildren == 0 && i == 0;
const unsigned int mesh_idx = node.mMeshes[i];
const aiMesh &mesh = *scene->mMeshes[mesh_idx];
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKINFO);
{
ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
WriteString(GetMeshName(mesh, mesh_idx, node));
writer.PutI4(0);
writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level));
++seq;
}
}
return seq;
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteMaterials() {
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL);
const aiMaterial &mat = *scene->mMaterials[i];
{
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME);
const std::string &name = GetMaterialName(mat, i);
WriteString(name);
}
aiColor3D color;
if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE);
WriteColor(color);
}
if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR);
WriteColor(color);
}
if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT);
WriteColor(color);
}
float f;
if (mat.Get(AI_MATKEY_OPACITY, f) == AI_SUCCESS) {
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TRANSPARENCY);
WritePercentChunk(1.0f - f);
}
if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM);
WriteColor(color);
}
if (aiShadingMode shading_mode = aiShadingMode_Flat; mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) {
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING);
Discreet3DS::shadetype3ds shading_mode_out;
switch (shading_mode) {
case aiShadingMode_Flat:
case aiShadingMode_NoShading:
shading_mode_out = Discreet3DS::Flat;
break;
case aiShadingMode_Gouraud:
case aiShadingMode_Toon:
case aiShadingMode_OrenNayar:
case aiShadingMode_Minnaert:
shading_mode_out = Discreet3DS::Gouraud;
break;
case aiShadingMode_Phong:
case aiShadingMode_Blinn:
case aiShadingMode_CookTorrance:
case aiShadingMode_Fresnel:
case aiShadingMode_PBR_BRDF: // Possibly should be Discreet3DS::Metal in some cases but this is undocumented
shading_mode_out = Discreet3DS::Phong;
break;
default:
shading_mode_out = Discreet3DS::Flat;
ai_assert(false);
}
writer.PutU2(static_cast<uint16_t>(shading_mode_out));
}
if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) {
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS);
WritePercentChunk(f);
}
if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) {
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT);
WritePercentChunk(f);
}
if (int twosided; mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) {
ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE);
writer.PutI2(1);
}
// Fallback to BASE_COLOR if no DIFFUSE
if (!WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE))
WriteTexture(mat, aiTextureType_BASE_COLOR, Discreet3DS::CHUNK_MAT_TEXTURE);
WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP);
WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP);
WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP);
WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP);
WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP);
WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP);
}
}
// ------------------------------------------------------------------------------------------------
// returns true if the texture existed
bool Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) {
aiString path;
aiTextureMapMode map_mode[2] = {
aiTextureMapMode_Wrap, aiTextureMapMode_Wrap
};
ai_real blend = 1.0;
if (mat.GetTexture(type, 0, &path, nullptr, nullptr, &blend, nullptr, map_mode) != AI_SUCCESS || !path.length) {
return false;
}
// TODO: handle embedded textures properly
if (path.data[0] == '*') {
ASSIMP_LOG_ERROR("Ignoring embedded texture for export: ", path.C_Str());
return false;
}
ChunkWriter chunk(writer, chunk_flags);
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPFILE);
WriteString(path);
}
WritePercentChunk(blend);
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING);
uint16_t val = 0; // WRAP
if (map_mode[0] == aiTextureMapMode_Mirror) {
val = 0x2;
} else if (map_mode[0] == aiTextureMapMode_Decal) {
val = 0x10;
}
writer.PutU2(val);
}
// TODO: export texture transformation (i.e. UV offset, scale, rotation)
return true;
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteMeshes() {
// NOTE: 3DS allows for instances. However:
// i) not all importers support reading them
// ii) instances are not as flexible as they are in assimp, in particular,
// nodes can carry (and instance) only one mesh.
//
// This exporter currently deep clones all instanced meshes, i.e. for each mesh
// attached to a node a full TRIMESH chunk is written to the file.
//
// Furthermore, the TRIMESH is transformed into world space so that it will
// appear correctly if importers don't read the scene hierarchy at all.
for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) {
const aiNode &node = *(*it).first;
const unsigned int mesh_idx = (*it).second;
const aiMesh &mesh = *scene->mMeshes[mesh_idx];
// This should not happen if the SLM step is correctly executed
// before the scene is handed to the exporter
ai_assert(mesh.mNumVertices <= 0xffff);
ai_assert(mesh.mNumFaces <= 0xffff);
const aiMatrix4x4 &trafo = trafos[&node];
ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK);
// Mesh name is tied to the node it is attached to so it can later be referenced
const std::string &name = GetMeshName(mesh, mesh_idx, node);
WriteString(name);
// TRIMESH chunk
ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH);
// Vertices in world space
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_VERTLIST);
const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
writer.PutU2(count);
for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
const aiVector3D &v = mesh.mVertices[i];
writer.PutF4(v.x);
writer.PutF4(v.y);
writer.PutF4(v.z);
}
}
// UV coordinates
if (mesh.HasTextureCoords(0)) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPLIST);
const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
writer.PutU2(count);
for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
const aiVector3D &v = mesh.mTextureCoords[0][i];
writer.PutF4(v.x);
writer.PutF4(v.y);
}
}
// Faces (indices)
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACELIST);
ai_assert(mesh.mNumFaces <= 0xffff);
// Count triangles, discard lines and points
uint16_t count = 0;
for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
const aiFace &f = mesh.mFaces[i];
if (f.mNumIndices < 3) {
continue;
}
// TRIANGULATE step is a pre-requisite so we should not see polys here
ai_assert(f.mNumIndices == 3);
++count;
}
writer.PutU2(count);
for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
const aiFace &f = mesh.mFaces[i];
if (f.mNumIndices < 3) {
continue;
}
for (unsigned int j = 0; j < 3; ++j) {
ai_assert(f.mIndices[j] <= 0xffff);
writer.PutI2(static_cast<uint16_t>(f.mIndices[j]));
}
// Edge visibility flag
writer.PutI2(0x0);
}
// TODO: write smoothing groups (CHUNK_SMOOLIST)
WriteFaceMaterialChunk(mesh);
}
// Transformation matrix by which the mesh vertices have been pre-transformed with.
{
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRMATRIX);
// Store rotation 3x3 matrix row wise
for (unsigned int r = 0; r < 3; ++r) {
for (unsigned int c = 0; c < 3; ++c) {
writer.PutF4(trafo[r][c]);
}
}
// Store translation sub vector column wise
for (unsigned int r = 0; r < 3; ++r) {
writer.PutF4(trafo[r][3]);
}
}
}
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh &mesh) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACEMAT);
const std::string &name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex);
WriteString(name);
// Because assimp splits meshes by material, only a single
// FACEMAT chunk needs to be written
ai_assert(mesh.mNumFaces <= 0xffff);
const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces);
writer.PutU2(count);
for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
writer.PutU2(static_cast<uint16_t>(i));
}
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteString(const std::string &s) {
for (auto it = s.begin(); it != s.end(); ++it) {
writer.PutI1(*it);
}
writer.PutI1('\0');
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteString(const aiString &s) {
for (std::size_t i = 0; i < s.length; ++i) {
writer.PutI1(s.data[i]);
}
writer.PutI1('\0');
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WriteColor(const aiColor3D &color) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_RGBF);
writer.PutF4(color.r);
writer.PutF4(color.g);
writer.PutF4(color.b);
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WritePercentChunk(float f) {
ChunkWriter curChunk(writer, Discreet3DS::CHUNK_PERCENTF);
writer.PutF4(f);
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSExporter::WritePercentChunk(double f) {
ChunkWriter ccurChunkhunk(writer, Discreet3DS::CHUNK_PERCENTD);
writer.PutF8(f);
}
#endif // ASSIMP_BUILD_NO_3DS_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,95 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file 3DSExporter.h
* 3DS Exporter Main Header
*/
#ifndef AI_3DSEXPORTER_H_INC
#define AI_3DSEXPORTER_H_INC
#include <map>
#include <memory>
#include <assimp/StreamWriter.h>
#include <assimp/material.h>
struct aiScene;
struct aiNode;
struct aiMaterial;
struct aiMesh;
namespace Assimp {
// ------------------------------------------------------------------------------------------------
/**
* @brief Helper class to export a given scene to a 3DS file.
*/
// ------------------------------------------------------------------------------------------------
class Discreet3DSExporter final {
public:
Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
~Discreet3DSExporter() = default;
private:
void WriteMeshes();
void WriteMaterials();
bool WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags);
void WriteFaceMaterialChunk(const aiMesh& mesh);
int WriteHierarchy(const aiNode& node, int level, int sibling_level);
void WriteString(const std::string& s);
void WriteString(const aiString& s);
void WriteColor(const aiColor3D& color);
void WritePercentChunk(float f);
void WritePercentChunk(double f);
private:
const aiScene* const scene;
StreamWriterLE writer;
std::map<const aiNode*, aiMatrix4x4> trafos;
using MeshesByNodeMap = std::multimap<const aiNode*, unsigned int>;
MeshesByNodeMap meshes;
};
} // Namespace Assimp
#endif // AI_3DSEXPORTER_H_INC

View File

@@ -0,0 +1,583 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file Defines helper data structures for the import of 3DS files */
#ifndef AI_3DSFILEHELPER_H_INC
#define AI_3DSFILEHELPER_H_INC
#include <assimp/SmoothingGroups.h>
#include <assimp/SpatialSort.h>
#include <assimp/StringUtils.h>
#include <assimp/anim.h>
#include <assimp/camera.h>
#include <assimp/light.h>
#include <assimp/material.h>
#include <assimp/qnan.h>
#include <cstdio> //sprintf
namespace Assimp::D3DS {
#include <assimp/Compiler/pushpack1.h>
// ---------------------------------------------------------------------------
/** Defines chunks and data structures.
*/
namespace Discreet3DS {
//! data structure for a single chunk in a .3ds file
struct Chunk {
uint16_t Flag;
uint32_t Size;
} PACK_STRUCT;
//! Used for shading field in material3ds structure
//! From AutoDesk 3ds SDK
typedef enum {
// translated to gouraud shading with wireframe active
Wire = 0x0,
// if this material is set, no vertex normals will
// be calculated for the model. Face normals + gouraud
Flat = 0x1,
// standard gouraud shading
Gouraud = 0x2,
// phong shading
Phong = 0x3,
// cooktorrance or anistropic phong shading ...
// the exact meaning is unknown, if you know it
// feel free to tell me ;-)
Metal = 0x4,
// required by the ASE loader
Blinn = 0x5
} shadetype3ds;
// Flags for animated keys
enum {
KEY_USE_TENS = 0x1,
KEY_USE_CONT = 0x2,
KEY_USE_BIAS = 0x4,
KEY_USE_EASE_TO = 0x8,
KEY_USE_EASE_FROM = 0x10
};
enum {
// ********************************************************************
// Basic chunks which can be found everywhere in the file
CHUNK_VERSION = 0x0002,
CHUNK_RGBF = 0x0010, // float4 R; float4 G; float4 B
CHUNK_RGBB = 0x0011, // int1 R; int1 G; int B
// Linear color values (gamma = 2.2?)
CHUNK_LINRGBF = 0x0013, // float4 R; float4 G; float4 B
CHUNK_LINRGBB = 0x0012, // int1 R; int1 G; int B
CHUNK_PERCENTW = 0x0030, // int2 percentage
CHUNK_PERCENTF = 0x0031, // float4 percentage
CHUNK_PERCENTD = 0x0032, // float8 percentage
// ********************************************************************
// Prj master chunk
CHUNK_PRJ = 0xC23D,
// MDLI master chunk
CHUNK_MLI = 0x3DAA,
// Primary main chunk of the .3ds file
CHUNK_MAIN = 0x4D4D,
// Mesh main chunk
CHUNK_OBJMESH = 0x3D3D,
// Specifies the background color of the .3ds file
// This is passed through the material system for
// viewing purposes.
CHUNK_BKGCOLOR = 0x1200,
// Specifies the ambient base color of the scene.
// This is added to all materials in the file
CHUNK_AMBCOLOR = 0x2100,
// Specifies the background image for the whole scene
// This value is passed through the material system
// to the viewer
CHUNK_BIT_MAP = 0x1100,
CHUNK_BIT_MAP_EXISTS = 0x1101,
// ********************************************************************
// Viewport related stuff. Ignored
CHUNK_DEFAULT_VIEW = 0x3000,
CHUNK_VIEW_TOP = 0x3010,
CHUNK_VIEW_BOTTOM = 0x3020,
CHUNK_VIEW_LEFT = 0x3030,
CHUNK_VIEW_RIGHT = 0x3040,
CHUNK_VIEW_FRONT = 0x3050,
CHUNK_VIEW_BACK = 0x3060,
CHUNK_VIEW_USER = 0x3070,
CHUNK_VIEW_CAMERA = 0x3080,
// ********************************************************************
// Mesh chunks
CHUNK_OBJBLOCK = 0x4000,
CHUNK_TRIMESH = 0x4100,
CHUNK_VERTLIST = 0x4110,
CHUNK_VERTFLAGS = 0x4111,
CHUNK_FACELIST = 0x4120,
CHUNK_FACEMAT = 0x4130,
CHUNK_MAPLIST = 0x4140,
CHUNK_SMOOLIST = 0x4150,
CHUNK_TRMATRIX = 0x4160,
CHUNK_MESHCOLOR = 0x4165,
CHUNK_TXTINFO = 0x4170,
CHUNK_LIGHT = 0x4600,
CHUNK_CAMERA = 0x4700,
CHUNK_HIERARCHY = 0x4F00,
// Specifies the global scaling factor. This is applied
// to the root node's transformation matrix
CHUNK_MASTER_SCALE = 0x0100,
// ********************************************************************
// Material chunks
CHUNK_MAT_MATERIAL = 0xAFFF,
// asciiz containing the name of the material
CHUNK_MAT_MATNAME = 0xA000,
CHUNK_MAT_AMBIENT = 0xA010, // followed by color chunk
CHUNK_MAT_DIFFUSE = 0xA020, // followed by color chunk
CHUNK_MAT_SPECULAR = 0xA030, // followed by color chunk
// Specifies the shininess of the material
// followed by percentage chunk
CHUNK_MAT_SHININESS = 0xA040,
CHUNK_MAT_SHININESS_PERCENT = 0xA041,
// Specifies the shading mode to be used
// followed by a short
CHUNK_MAT_SHADING = 0xA100,
// NOTE: Emissive color (self illumination) seems not
// to be a color but a single value, type is unknown.
// Make the parser accept both of them.
// followed by percentage chunk (?)
CHUNK_MAT_SELF_ILLUM = 0xA080,
// Always followed by percentage chunk (?)
CHUNK_MAT_SELF_ILPCT = 0xA084,
// Always followed by percentage chunk
CHUNK_MAT_TRANSPARENCY = 0xA050,
// Diffuse texture channel 0
CHUNK_MAT_TEXTURE = 0xA200,
// Contains opacity information for each texel
CHUNK_MAT_OPACMAP = 0xA210,
// Contains a reflection map to be used to reflect
// the environment. This is partially supported.
CHUNK_MAT_REFLMAP = 0xA220,
// Self Illumination map (emissive colors)
CHUNK_MAT_SELFIMAP = 0xA33d,
// Bumpmap. Not specified whether it is a heightmap
// or a normal map. Assme it is a heightmap since
// artist normally prefer this format.
CHUNK_MAT_BUMPMAP = 0xA230,
// Specular map. Seems to influence the specular color
CHUNK_MAT_SPECMAP = 0xA204,
// Holds shininess data.
CHUNK_MAT_MAT_SHINMAP = 0xA33C,
// Scaling in U/V direction.
// (need to gen separate UV coordinate set
// and do this by hand)
CHUNK_MAT_MAP_USCALE = 0xA354,
CHUNK_MAT_MAP_VSCALE = 0xA356,
// Translation in U/V direction.
// (need to gen separate UV coordinate set
// and do this by hand)
CHUNK_MAT_MAP_UOFFSET = 0xA358,
CHUNK_MAT_MAP_VOFFSET = 0xA35a,
// UV-coordinates rotation around the z-axis
// Assumed to be in radians.
CHUNK_MAT_MAP_ANG = 0xA35C,
// Tiling flags for 3DS files
CHUNK_MAT_MAP_TILING = 0xa351,
// Specifies the file name of a texture
CHUNK_MAPFILE = 0xA300,
// Specifies whether a material requires two-sided rendering
CHUNK_MAT_TWO_SIDE = 0xA081,
// ********************************************************************
// Main keyframer chunk. Contains translation/rotation/scaling data
CHUNK_KEYFRAMER = 0xB000,
// Supported sub chunks
CHUNK_TRACKINFO = 0xB002,
CHUNK_TRACKOBJNAME = 0xB010,
CHUNK_TRACKDUMMYOBJNAME = 0xB011,
CHUNK_TRACKPIVOT = 0xB013,
CHUNK_TRACKPOS = 0xB020,
CHUNK_TRACKROTATE = 0xB021,
CHUNK_TRACKSCALE = 0xB022,
// ********************************************************************
// Keyframes for various other stuff in the file
// Partially ignored
CHUNK_AMBIENTKEY = 0xB001,
CHUNK_TRACKMORPH = 0xB026,
CHUNK_TRACKHIDE = 0xB029,
CHUNK_OBJNUMBER = 0xB030,
CHUNK_TRACKCAMERA = 0xB003,
CHUNK_TRACKFOV = 0xB023,
CHUNK_TRACKROLL = 0xB024,
CHUNK_TRACKCAMTGT = 0xB004,
CHUNK_TRACKLIGHT = 0xB005,
CHUNK_TRACKLIGTGT = 0xB006,
CHUNK_TRACKSPOTL = 0xB007,
CHUNK_FRAMES = 0xB008,
// ********************************************************************
// light sub-chunks
CHUNK_DL_OFF = 0x4620,
CHUNK_DL_OUTER_RANGE = 0x465A,
CHUNK_DL_INNER_RANGE = 0x4659,
CHUNK_DL_MULTIPLIER = 0x465B,
CHUNK_DL_EXCLUDE = 0x4654,
CHUNK_DL_ATTENUATE = 0x4625,
CHUNK_DL_SPOTLIGHT = 0x4610,
// camera sub-chunks
CHUNK_CAM_RANGES = 0x4720
};
}
// ---------------------------------------------------------------------------
/** Helper structure representing a 3ds mesh face */
struct Face : public FaceWithSmoothingGroup {
};
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4315)
#endif // _MSC_VER
// ---------------------------------------------------------------------------
/** Helper structure representing a texture */
struct Texture {
//! Default constructor
Texture() AI_NO_EXCEPT
: mTextureBlend(0.0f),
mOffsetU(0.0),
mOffsetV(0.0),
mScaleU(1.0),
mScaleV(1.0),
mRotation(0.0),
mMapMode(aiTextureMapMode_Wrap),
bPrivate(),
iUVSrc(0) {
mTextureBlend = get_qnan();
}
Texture(const Texture &other) = default;
Texture(Texture &&other) AI_NO_EXCEPT = default;
Texture &operator=(Texture &&other) AI_NO_EXCEPT = default;
//! Specifies the blend factor for the texture
ai_real mTextureBlend;
//! Specifies the filename of the texture
std::string mMapName;
//! Specifies texture coordinate offsets/scaling/rotations
ai_real mOffsetU;
ai_real mOffsetV;
ai_real mScaleU;
ai_real mScaleV;
ai_real mRotation;
//! Specifies the mapping mode to be used for the texture
aiTextureMapMode mMapMode;
//! Used internally
bool bPrivate;
int iUVSrc;
};
#include <assimp/Compiler/poppack1.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
// ---------------------------------------------------------------------------
/** Helper structure representing a 3ds material */
struct Material {
//! Default constructor has been deleted
Material() :
mName(),
mDiffuse(0.6f, 0.6f, 0.6f),
mSpecularExponent(ai_real(0.0)),
mShininessStrength(ai_real(1.0)),
mShading(Discreet3DS::Gouraud),
mTransparency(ai_real(1.0)),
mBumpHeight(ai_real(1.0)),
mTwoSided(false) {
// empty
}
//! Constructor with explicit name
explicit Material(const std::string &name) :
mName(name),
mDiffuse(0.6f, 0.6f, 0.6f),
mSpecularExponent(ai_real(0.0)),
mShininessStrength(ai_real(1.0)),
mShading(Discreet3DS::Gouraud),
mTransparency(ai_real(1.0)),
mBumpHeight(ai_real(1.0)),
mTwoSided(false) {
// empty
}
Material(const Material &other) = default;
virtual ~Material() = default;
//! Name of the material
std::string mName;
//! Diffuse color of the material
aiColor3D mDiffuse;
//! Specular exponent
ai_real mSpecularExponent;
//! Shininess strength, in percent
ai_real mShininessStrength;
//! Specular color of the material
aiColor3D mSpecular;
//! Ambient color of the material
aiColor3D mAmbient;
//! Shading type to be used
Discreet3DS::shadetype3ds mShading;
//! Opacity of the material
ai_real mTransparency;
//! Diffuse texture channel
Texture sTexDiffuse;
//! Opacity texture channel
Texture sTexOpacity;
//! Specular texture channel
Texture sTexSpecular;
//! Reflective texture channel
Texture sTexReflective;
//! Bump texture channel
Texture sTexBump;
//! Emissive texture channel
Texture sTexEmissive;
//! Shininess texture channel
Texture sTexShininess;
//! Scaling factor for the bump values
ai_real mBumpHeight;
//! Emissive color
aiColor3D mEmissive;
//! Ambient texture channel
//! (used by the ASE format)
Texture sTexAmbient;
//! True if the material must be rendered from two sides
bool mTwoSided;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent a 3ds file mesh */
struct Mesh : public MeshWithSmoothingGroups<D3DS::Face> {
//! Default constructor has been deleted
Mesh() = delete;
//! Constructor with explicit name
explicit Mesh(const std::string &name) :
mName(name) {
}
//! Name of the mesh
std::string mName;
//! Texture coordinates
std::vector<aiVector3D> mTexCoords;
//! Face materials
std::vector<unsigned int> mFaceMaterials;
//! Local transformation matrix
aiMatrix4x4 mMat;
};
// ---------------------------------------------------------------------------
/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the
C-API, so it would be difficult to make them a template. */
struct aiFloatKey {
double mTime; ///< The time of this key
ai_real mValue; ///< The value of this key
#ifdef __cplusplus
// time is not compared
bool operator==(const aiFloatKey &o) const { return o.mValue == this->mValue; }
bool operator!=(const aiFloatKey &o) const { return o.mValue != this->mValue; }
// Only time is compared. This operator is defined
// for use with std::sort
bool operator<(const aiFloatKey &o) const { return mTime < o.mTime; }
bool operator>(const aiFloatKey &o) const { return mTime > o.mTime; }
#endif
};
// ---------------------------------------------------------------------------
/** Helper structure to represent a 3ds file node */
struct Node {
Node() = delete;
explicit Node(const std::string &name) :
mParent(nullptr),
mName(name),
mInstanceNumber(0),
mHierarchyPos(0),
mHierarchyIndex(0),
mInstanceCount(1) {
aRotationKeys.reserve(20);
aPositionKeys.reserve(20);
aScalingKeys.reserve(20);
}
~Node() {
for (unsigned int i = 0; i < mChildren.size(); ++i)
delete mChildren[i];
}
//! Pointer to the parent node
Node *mParent;
//! Holds all child nodes
std::vector<Node *> mChildren;
//! Name of the node
std::string mName;
//! InstanceNumber of the node
int32_t mInstanceNumber;
//! Dummy nodes: real name to be combined with the $$$DUMMY
std::string mDummyName;
//! Position of the node in the hierarchy (tree depth)
int16_t mHierarchyPos;
//! Index of the node
int16_t mHierarchyIndex;
//! Rotation keys loaded from the file
std::vector<aiQuatKey> aRotationKeys;
//! Position keys loaded from the file
std::vector<aiVectorKey> aPositionKeys;
//! Scaling keys loaded from the file
std::vector<aiVectorKey> aScalingKeys;
// For target lights (spot lights and directional lights):
// The position of the target
std::vector<aiVectorKey> aTargetPositionKeys;
// For cameras: the camera roll angle
std::vector<aiFloatKey> aCameraRollKeys;
//! Pivot position loaded from the file
aiVector3D vPivot;
//instance count, will be kept only for the first node
int32_t mInstanceCount;
//! Add a child node, setup the right parent node for it
//! \param pc Node to be 'adopted'
inline Node &push_back(Node *pc) {
mChildren.push_back(pc);
pc->mParent = this;
return *this;
}
};
// ---------------------------------------------------------------------------
/** Helper structure analogue to aiScene */
struct Scene {
//! List of all materials loaded
//! NOTE: 3ds references materials globally
std::vector<Material> mMaterials;
//! List of all meshes loaded
std::vector<Mesh> mMeshes;
//! List of all cameras loaded
std::vector<aiCamera *> mCameras;
//! List of all lights loaded
std::vector<aiLight *> mLights;
//! Pointer to the root node of the scene
// --- moved to main class
// Node* pcRootNode;
};
} // end of namespace Assimp::D3DS
#endif // AI_XFILEHELPER_H_INC

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file 3DSLoader.h
* @brief 3DS File format loader
*/
#ifndef AI_3DSIMPORTER_H_INC
#define AI_3DSIMPORTER_H_INC
#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
#include <assimp/BaseImporter.h>
#include <assimp/types.h>
#include "3DSHelper.h"
#include <assimp/StreamReader.h>
struct aiNode;
namespace Assimp {
// ---------------------------------------------------------------------------------
/** Importer class for 3D Studio r3 and r4 3DS files
*/
class Discreet3DSImporter final : public BaseImporter {
public:
Discreet3DSImporter();
~Discreet3DSImporter() override = default;
// -------------------------------------------------------------------
/** Returns whether the class can handle the format of the given file.
* See BaseImporter::CanRead() for details.
*/
bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
bool checkSig) const override;
// -------------------------------------------------------------------
/** Called prior to ReadFile().
* The function is a request to the importer to update its configuration
* basing on the Importer's configuration property list.
*/
void SetupProperties(const Importer* pImp) override;
protected:
// -------------------------------------------------------------------
/** Return importer meta information.
* See #BaseImporter::GetInfo for the details
*/
const aiImporterDesc* GetInfo () const override;
// -------------------------------------------------------------------
/** Imports the given file into the given scene structure.
* See BaseImporter::InternReadFile() for details
*/
void InternReadFile( const std::string& pFile, aiScene* pScene,
IOSystem* pIOHandler) override;
// -------------------------------------------------------------------
/** Converts a temporary material to the outer representation
*/
void ConvertMaterial(D3DS::Material& p_cMat, aiMaterial& p_pcOut);
// -------------------------------------------------------------------
/** Read a chunk
*
* @param pcOut Receives the current chunk
*/
void ReadChunk(D3DS::Discreet3DS::Chunk* pcOut);
// -------------------------------------------------------------------
/** Parse a percentage chunk. mCurrent will point to the next
* chunk behind afterwards. If no percentage chunk is found
* QNAN is returned.
*/
ai_real ParsePercentageChunk();
// -------------------------------------------------------------------
/** Parse a color chunk. mCurrent will point to the next chunk behind
* afterward. If no color chunk is found QNAN is returned in all members.
*/
void ParseColorChunk(aiColor3D* p_pcOut, bool p_bAcceptPercent = true);
// -------------------------------------------------------------------
/** Skip a chunk in the file
*/
void SkipChunk();
// -------------------------------------------------------------------
/** Generate the node-graph
*/
void GenerateNodeGraph(aiScene* pcOut);
// -------------------------------------------------------------------
/** Parse a main top-level chunk in the file
*/
void ParseMainChunk();
// -------------------------------------------------------------------
/** Parse a top-level chunk in the file
*/
void ParseChunk(const char* name, unsigned int num);
// -------------------------------------------------------------------
/** Parse a top-level editor chunk in the file
*/
void ParseEditorChunk();
// -------------------------------------------------------------------
/** Parse a top-level object chunk in the file
*/
void ParseObjectChunk();
// -------------------------------------------------------------------
/** Parse a material chunk in the file
*/
void ParseMaterialChunk();
// -------------------------------------------------------------------
/** Parse a mesh chunk in the file
*/
void ParseMeshChunk();
// -------------------------------------------------------------------
/** Parse a light chunk in the file
*/
void ParseLightChunk();
// -------------------------------------------------------------------
/** Parse a camera chunk in the file
*/
void ParseCameraChunk();
// -------------------------------------------------------------------
/** Parse a face list chunk in the file
*/
void ParseFaceChunk();
// -------------------------------------------------------------------
/** Parse a keyframe chunk in the file
*/
void ParseKeyframeChunk();
// -------------------------------------------------------------------
/** Parse a hierarchy chunk in the file
*/
void ParseHierarchyChunk(uint16_t parent);
// -------------------------------------------------------------------
/** Parse a texture chunk in the file
*/
void ParseTextureChunk(D3DS::Texture* pcOut);
// -------------------------------------------------------------------
/** Convert the meshes in the file
*/
void ConvertMeshes(aiScene* pcOut);
// -------------------------------------------------------------------
/** Replace the default material in the scene
*/
void ReplaceDefaultMaterial();
bool ContainsTextures(unsigned int i) const {
return !mScene->mMaterials[i].sTexDiffuse.mMapName.empty() ||
!mScene->mMaterials[i].sTexBump.mMapName.empty() ||
!mScene->mMaterials[i].sTexOpacity.mMapName.empty() ||
!mScene->mMaterials[i].sTexEmissive.mMapName.empty() ||
!mScene->mMaterials[i].sTexSpecular.mMapName.empty() ||
!mScene->mMaterials[i].sTexShininess.mMapName.empty() ;
}
// -------------------------------------------------------------------
/** Convert the whole scene
*/
void ConvertScene(aiScene* pcOut);
// -------------------------------------------------------------------
/** generate unique vertices for a mesh
*/
void MakeUnique(D3DS::Mesh& sMesh);
// -------------------------------------------------------------------
/** Add a node to the node graph
*/
void AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut, D3DS::Node* pcIn,
aiMatrix4x4& absTrafo);
// -------------------------------------------------------------------
/** Search for a node in the graph.
* Called recursively
*/
void InverseNodeSearch(D3DS::Node* pcNode, D3DS::Node* pcCurrent);
// -------------------------------------------------------------------
/** Apply the master scaling factor to the mesh
*/
void ApplyMasterScale(const aiScene* pScene);
// -------------------------------------------------------------------
/** Clamp all indices in the file to a valid range
*/
void CheckIndices(D3DS::Mesh& sMesh);
// -------------------------------------------------------------------
/** Skip the TCB info in a track key
*/
void SkipTCBInfo();
private:
/// Stream to read from
StreamReaderLE* mStream;
/// Last touched node index
short mLastNodeIndex;
/// Current node
D3DS::Node* mCurrentNode;
/// Root node
D3DS::Node *mRootNode;
/// Scene under construction
D3DS::Scene* mScene;
/// Ambient base color of the scene
aiColor3D mClrAmbient;
/// Master scaling factor of the scene
ai_real mMasterScale;
/// Path to the background image of the scene
std::string mBackgroundImage;
/// true for has a background
bool bHasBG;
/// true if PRJ file
bool bIsPrj;
};
} // end of namespace Assimp
#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER
#endif // AI_3DSIMPORTER_H_INC

View File

@@ -0,0 +1,168 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#pragma once
#include <assimp/vector3.h>
#include <assimp/matrix4x4.h>
#include <assimp/ParsingUtils.h>
#include <vector>
#include <string>
struct aiMaterial;
struct aiMesh;
namespace Assimp:: D3MF {
enum class ResourceType {
RT_Object,
RT_BaseMaterials,
RT_EmbeddedTexture2D,
RT_Texture2DGroup,
RT_ColorGroup,
RT_Unknown
}; // To be extended with other resource types (eg. material extension resources like Texture2d, Texture2dGroup...)
class Resource {
public:
int mId;
explicit Resource(int id) : mId(id) {
// empty
}
virtual ~Resource() = default;
virtual ResourceType getType() const {
return ResourceType::RT_Unknown;
}
};
class EmbeddedTexture final : public Resource {
public:
std::string mPath;
std::string mContentType;
std::string mTilestyleU;
std::string mTilestyleV;
std::vector<char> mBuffer;
explicit EmbeddedTexture(int id) : Resource(id) {
// empty
}
~EmbeddedTexture() override = default;
ResourceType getType() const override {
return ResourceType::RT_EmbeddedTexture2D;
}
};
class Texture2DGroup final : public Resource {
public:
std::vector<aiVector2D> mTex2dCoords;
int mTexId;
explicit Texture2DGroup(int id) : Resource(id), mTexId(-1) {
// empty
}
~Texture2DGroup() override = default;
ResourceType getType() const override {
return ResourceType::RT_Texture2DGroup;
}
};
class ColorGroup final : public Resource {
public:
std::vector<aiColor4D> mColors;
explicit ColorGroup(int id) : Resource(id) {
// empty
}
~ColorGroup() override = default;
ResourceType getType() const override {
return ResourceType::RT_ColorGroup;
}
};
class BaseMaterials final : public Resource {
public:
std::vector<unsigned int> mMaterialIndex;
explicit BaseMaterials(int id) : Resource(id) {
// empty
}
~BaseMaterials() override = default;
ResourceType getType() const override {
return ResourceType::RT_BaseMaterials;
}
};
struct Component {
int mObjectId;
aiMatrix4x4 mTransformation;
};
class Object final : public Resource {
public:
std::vector<aiMesh *> mMeshes;
std::vector<unsigned int> mMeshIndex;
std::vector<Component> mComponents;
std::string mName;
explicit Object(int id) :
Resource(id),
mName(std::string("Object_") + ai_to_string(id)) {
// empty
}
~Object() override = default;
ResourceType getType() const override {
return ResourceType::RT_Object;
}
};
} // namespace Assimp::D3MF

View File

@@ -0,0 +1,119 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#pragma once
namespace Assimp::D3MF::XmlTag {
// Root tag
constexpr char RootTag[] = "3MF";
// Meta-data
constexpr char meta[] = "metadata";
constexpr char meta_name[] = "name";
// Model-data specific tags
constexpr char model[] = "model";
constexpr char model_unit[] = "unit";
constexpr char metadata[] = "metadata";
constexpr char resources[] = "resources";
constexpr char object[] = "object";
constexpr char mesh[] = "mesh";
constexpr char components[] = "components";
constexpr char component[] = "component";
constexpr char vertices[] = "vertices";
constexpr char vertex[] = "vertex";
constexpr char triangles[] = "triangles";
constexpr char triangle[] = "triangle";
constexpr char x[] = "x";
constexpr char y[] = "y";
constexpr char z[] = "z";
constexpr char v1[] = "v1";
constexpr char v2[] = "v2";
constexpr char v3[] = "v3";
constexpr char id[] = "id";
constexpr char pid[] = "pid";
constexpr char pindex[] = "pindex";
constexpr char p1[] = "p1";
constexpr char p2[] = "p2";
constexpr char p3[] = "p3";
constexpr char name[] = "name";
constexpr char type[] = "type";
constexpr char build[] = "build";
constexpr char item[] = "item";
constexpr char objectid[] = "objectid";
constexpr char transform[] = "transform";
constexpr char path[] = "path";
// Material definitions
constexpr char basematerials[] = "basematerials";
constexpr char basematerials_base[] = "base";
constexpr char basematerials_name[] = "name";
constexpr char basematerials_displaycolor[] = "displaycolor";
constexpr char texture_2d[] = "m:texture2d";
constexpr char texture_group[] = "m:texture2dgroup";
constexpr char texture_content_type[] = "contenttype";
constexpr char texture_tilestyleu[] = "tilestyleu";
constexpr char texture_tilestylev[] = "tilestylev";
constexpr char texture_2d_coord[] = "m:tex2coord";
constexpr char texture_cuurd_u[] = "u";
constexpr char texture_cuurd_v[] = "v";
// vertex color definitions
constexpr char colorgroup[] = "m:colorgroup";
constexpr char color_item[] = "m:color";
constexpr char color_value[] = "color";
// Meta info tags
constexpr char CONTENT_TYPES_ARCHIVE[] = "[Content_Types].xml";
constexpr char ROOT_RELATIONSHIPS_ARCHIVE[] = "_rels/.rels";
constexpr char SCHEMA_CONTENTTYPES[] = "http://schemas.openxmlformats.org/package/2006/content-types";
constexpr char SCHEMA_RELATIONSHIPS[] = "http://schemas.openxmlformats.org/package/2006/relationships";
constexpr char RELS_RELATIONSHIP_CONTAINER[] = "Relationships";
constexpr char RELS_RELATIONSHIP_NODE[] = "Relationship";
constexpr char RELS_ATTRIB_TARGET[] = "Target";
constexpr char RELS_ATTRIB_TYPE[] = "Type";
constexpr char RELS_ATTRIB_ID[] = "Id";
constexpr char PACKAGE_START_PART_RELATIONSHIP_TYPE[] = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel";
constexpr char PACKAGE_PRINT_TICKET_RELATIONSHIP_TYPE[] = "http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket";
constexpr char PACKAGE_TEXTURE_RELATIONSHIP_TYPE[] = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture";
constexpr char PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE[] = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
constexpr char PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE[] = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
} // namespace Assimp::D3MF

View File

@@ -0,0 +1,402 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
#include "D3MFExporter.h"
#include <assimp/Exceptional.h>
#include <assimp/StringUtils.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/Exporter.hpp>
#include <assimp/IOSystem.hpp>
#include "3MFXmlTags.h"
#include "D3MFOpcPackage.h"
#ifdef ASSIMP_USE_HUNTER
#include <zip/zip.h>
#else
#include <contrib/zip/src/zip.h>
#endif
namespace Assimp {
void ExportScene3MF(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
if (nullptr == pIOSystem) {
throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile));
}
D3MF::D3MFExporter myExporter(pFile, pScene);
if (myExporter.validate()) {
if (pIOSystem->Exists(pFile)) {
if (!pIOSystem->DeleteFile(pFile)) {
throw DeadlyExportError("File exists, cannot override : " + std::string(pFile));
}
}
bool ok = myExporter.exportArchive(pFile);
if (!ok) {
throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile));
}
}
}
namespace D3MF {
D3MFExporter::D3MFExporter(const char *pFile, const aiScene *pScene) :
mArchiveName(pFile), m_zipArchive(nullptr), mScene(pScene) {
// empty
}
D3MFExporter::~D3MFExporter() {
for (size_t i = 0; i < mRelations.size(); ++i) {
delete mRelations[i];
}
mRelations.clear();
}
bool D3MFExporter::validate() const {
if (mArchiveName.empty()) {
return false;
}
if (nullptr == mScene) {
return false;
}
return true;
}
bool D3MFExporter::exportArchive(const char *file) {
bool ok(true);
m_zipArchive = zip_open(file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
if (nullptr == m_zipArchive) {
return false;
}
ok |= exportContentTypes();
ok |= export3DModel();
ok |= exportRelations();
zip_close(m_zipArchive);
m_zipArchive = nullptr;
return ok;
}
bool D3MFExporter::exportContentTypes() {
mContentOutput.clear();
mContentOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
mContentOutput << std::endl;
mContentOutput << "<Types xmlns = \"http://schemas.openxmlformats.org/package/2006/content-types\">";
mContentOutput << std::endl;
mContentOutput << "<Default Extension = \"rels\" ContentType = \"application/vnd.openxmlformats-package.relationships+xml\" />";
mContentOutput << std::endl;
mContentOutput << "<Default Extension = \"model\" ContentType = \"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />";
mContentOutput << std::endl;
mContentOutput << "</Types>";
mContentOutput << std::endl;
zipContentType(XmlTag::CONTENT_TYPES_ARCHIVE);
return true;
}
bool D3MFExporter::exportRelations() {
mRelOutput.clear();
mRelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
mRelOutput << std::endl;
mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">";
for (size_t i = 0; i < mRelations.size(); ++i) {
if (mRelations[i]->target[0] == '/') {
mRelOutput << "<Relationship Target=\"" << mRelations[i]->target << "\" ";
} else {
mRelOutput << "<Relationship Target=\"/" << mRelations[i]->target << "\" ";
}
mRelOutput << "Id=\"" << mRelations[i]->id << "\" ";
mRelOutput << "Type=\"" << mRelations[i]->type << "\" />";
mRelOutput << std::endl;
}
mRelOutput << "</Relationships>";
mRelOutput << std::endl;
zipRelInfo("_rels", ".rels");
mRelOutput.flush();
return true;
}
bool D3MFExporter::export3DModel() {
mModelOutput.clear();
writeHeader();
mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\""
<< " xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">"
<< std::endl;
mModelOutput << "<" << XmlTag::resources << ">";
mModelOutput << std::endl;
writeMetaData();
writeBaseMaterials();
writeObjects();
mModelOutput << "</" << XmlTag::resources << ">";
mModelOutput << std::endl;
writeBuild();
mModelOutput << "</" << XmlTag::model << ">\n";
OpcPackageRelationship *info = new OpcPackageRelationship;
info->id = "rel0";
info->target = "/3D/3DModel.model";
info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
mRelations.push_back(info);
zipModel("3D", "3DModel.model");
mModelOutput.flush();
return true;
}
void D3MFExporter::writeHeader() {
mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
mModelOutput << std::endl;
}
void D3MFExporter::writeMetaData() {
if (nullptr == mScene->mMetaData) {
return;
}
const unsigned int numMetaEntries(mScene->mMetaData->mNumProperties);
if (0 == numMetaEntries) {
return;
}
const aiString *key = nullptr;
const aiMetadataEntry *entry(nullptr);
for (size_t i = 0; i < numMetaEntries; ++i) {
mScene->mMetaData->Get(i, key, entry);
std::string k(key->C_Str());
aiString value;
mScene->mMetaData->Get(k, value);
mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">";
mModelOutput << value.C_Str();
mModelOutput << "</" << XmlTag::meta << ">" << std::endl;
}
}
void D3MFExporter::writeBaseMaterials() {
mModelOutput << "<basematerials id=\"1\">\n";
std::string strName, hexDiffuseColor, tmp;
for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
aiMaterial *mat = mScene->mMaterials[i];
aiString name;
if (mat->Get(AI_MATKEY_NAME, name) != aiReturn_SUCCESS) {
strName = "basemat_" + ai_to_string(i);
} else {
strName = name.C_Str();
}
aiColor4D color;
if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS) {
hexDiffuseColor.clear();
tmp.clear();
// rgbs %
if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) {
hexDiffuseColor = ai_rgba2hex(
(int)(((ai_real)color.r) * 255),
(int)(((ai_real)color.g) * 255),
(int)(((ai_real)color.b) * 255),
(int)(((ai_real)color.a) * 255),
true);
} else {
hexDiffuseColor = "#";
tmp = ai_decimal_to_hexa((ai_real)color.r);
hexDiffuseColor += tmp;
tmp = ai_decimal_to_hexa((ai_real)color.g);
hexDiffuseColor += tmp;
tmp = ai_decimal_to_hexa((ai_real)color.b);
hexDiffuseColor += tmp;
tmp = ai_decimal_to_hexa((ai_real)color.a);
hexDiffuseColor += tmp;
}
} else {
hexDiffuseColor = "#FFFFFFFF";
}
mModelOutput << "<base name=\"" + strName + "\" " + " displaycolor=\"" + hexDiffuseColor + "\" />\n";
}
mModelOutput << "</basematerials>\n";
}
void D3MFExporter::writeObjects() {
if (nullptr == mScene->mRootNode) {
return;
}
aiNode *root = mScene->mRootNode;
for (unsigned int i = 0; i < root->mNumChildren; ++i) {
aiNode *currentNode(root->mChildren[i]);
if (nullptr == currentNode) {
continue;
}
mModelOutput << "<" << XmlTag::object << " id=\"" << i + 2 << "\" type=\"model\">";
mModelOutput << std::endl;
for (unsigned int j = 0; j < currentNode->mNumMeshes; ++j) {
aiMesh *currentMesh = mScene->mMeshes[currentNode->mMeshes[j]];
if (nullptr == currentMesh) {
continue;
}
writeMesh(currentMesh);
}
mBuildItems.push_back(i);
mModelOutput << "</" << XmlTag::object << ">";
mModelOutput << std::endl;
}
}
void D3MFExporter::writeMesh(aiMesh *mesh) {
if (nullptr == mesh) {
return;
}
mModelOutput << "<"
<< XmlTag::mesh
<< ">" << "\n";
mModelOutput << "<"
<< XmlTag::vertices
<< ">" << "\n";
for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
writeVertex(mesh->mVertices[i]);
}
mModelOutput << "</"
<< XmlTag::vertices << ">"
<< "\n";
const unsigned int matIdx(mesh->mMaterialIndex);
writeFaces(mesh, matIdx);
mModelOutput << "</"
<< XmlTag::mesh << ">"
<< "\n";
}
void D3MFExporter::writeVertex(const aiVector3D &pos) {
mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />";
mModelOutput << std::endl;
}
void D3MFExporter::writeFaces(aiMesh *mesh, unsigned int matIdx) {
if (nullptr == mesh) {
return;
}
if (!mesh->HasFaces()) {
return;
}
mModelOutput << "<"
<< XmlTag::triangles << ">"
<< "\n";
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
aiFace &currentFace = mesh->mFaces[i];
mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[0] << "\" v2=\""
<< currentFace.mIndices[1] << "\" v3=\"" << currentFace.mIndices[2]
<< "\" pid=\"1\" p1=\"" + ai_to_string(matIdx) + "\" />";
mModelOutput << "\n";
}
mModelOutput << "</"
<< XmlTag::triangles
<< ">";
mModelOutput << "\n";
}
void D3MFExporter::writeBuild() {
mModelOutput << "<"
<< XmlTag::build
<< ">"
<< "\n";
for (size_t i = 0; i < mBuildItems.size(); ++i) {
mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 2 << "\"/>";
mModelOutput << "\n";
}
mModelOutput << "</" << XmlTag::build << ">";
mModelOutput << "\n";
}
void D3MFExporter::zipContentType(const std::string &filename) {
addFileInZip(filename, mContentOutput.str());
}
void D3MFExporter::zipModel(const std::string &folder, const std::string &modelName) {
const std::string entry = folder + "/" + modelName;
addFileInZip(entry, mModelOutput.str());
}
void D3MFExporter::zipRelInfo(const std::string &folder, const std::string &relName) {
const std::string entry = folder + "/" + relName;
addFileInZip(entry, mRelOutput.str());
}
void D3MFExporter::addFileInZip(const std::string& entry, const std::string& content) {
if (nullptr == m_zipArchive) {
throw DeadlyExportError("3MF-Export: Zip archive not valid, nullptr.");
}
zip_entry_open(m_zipArchive, entry.c_str());
zip_entry_write(m_zipArchive, content.c_str(), content.size());
zip_entry_close(m_zipArchive);
}
} // Namespace D3MF
} // Namespace Assimp
#endif // ASSIMP_BUILD_NO_3MF_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,111 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#pragma once
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
#include <memory>
#include <sstream>
#include <vector>
#include <assimp/vector3.h>
struct aiScene;
struct aiNode;
struct aiMaterial;
struct aiMesh;
struct zip_t;
namespace Assimp {
class IOStream;
namespace D3MF {
struct OpcPackageRelationship;
class D3MFExporter {
public:
D3MFExporter( const char* pFile, const aiScene* pScene );
~D3MFExporter();
bool validate() const;
bool exportArchive( const char *file );
bool exportContentTypes();
bool exportRelations();
bool export3DModel();
protected:
void writeHeader();
void writeMetaData();
void writeBaseMaterials();
void writeObjects();
void writeMesh( aiMesh *mesh );
void writeVertex( const aiVector3D &pos );
void writeFaces( aiMesh *mesh, unsigned int matIdx );
void writeBuild();
// Zip the data
void zipContentType( const std::string &filename );
void zipModel( const std::string &folder, const std::string &modelName );
void zipRelInfo( const std::string &folder, const std::string &relName );
void addFileInZip( const std::string &entry, const std::string &content );
private:
std::string mArchiveName;
zip_t *m_zipArchive;
const aiScene *mScene;
std::ostringstream mModelOutput;
std::ostringstream mRelOutput;
std::ostringstream mContentOutput;
std::vector<unsigned int> mBuildItems;
std::vector<OpcPackageRelationship*> mRelations;
};
} // Namespace D3MF
} // Namespace Assimp
#endif // ASSIMP_BUILD_NO_3MF_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,126 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER
#include "D3MFImporter.h"
#include "3MFXmlTags.h"
#include "D3MFOpcPackage.h"
#include "XmlSerializer.h"
#include <assimp/StringComparison.h>
#include <assimp/StringUtils.h>
#include <assimp/XmlParser.h>
#include <assimp/ZipArchiveIOSystem.h>
#include <assimp/importerdesc.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/IOSystem.hpp>
#include <assimp/fast_atof.h>
#include <cassert>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <iomanip>
#include <cstring>
namespace Assimp {
using namespace D3MF;
static constexpr aiImporterDesc desc = {
"3mf Importer",
"",
"",
"http://3mf.io/",
aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour,
0,
0,
0,
0,
"3mf"
};
bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool ) const {
if (!ZipArchiveIOSystem::isZipArchive(pIOHandler, filename)) {
return false;
}
static constexpr char ModelRef[] = "3D/3dmodel.model";
ZipArchiveIOSystem archive(pIOHandler, filename);
if (!archive.Exists(ModelRef)) {
return false;
}
return true;
}
void D3MFImporter::SetupProperties(const Importer*) {
// empty
}
const aiImporterDesc *D3MFImporter::GetInfo() const {
return &desc;
}
void D3MFImporter::InternReadFile(const std::string &filename, aiScene *pScene, IOSystem *pIOHandler) {
D3MFOpcPackage opcPackage(pIOHandler, filename);
XmlParser xmlParser;
if (xmlParser.parse(opcPackage.RootStream())) {
XmlSerializer xmlSerializer(xmlParser);
xmlSerializer.ImportXml(pScene);
const std::vector<aiTexture*> &tex = opcPackage.GetEmbeddedTextures();
if (!tex.empty()) {
pScene->mNumTextures = static_cast<unsigned int>(tex.size());
pScene->mTextures = new aiTexture *[pScene->mNumTextures];
for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {
pScene->mTextures[i] = tex[i];
}
}
}
}
} // Namespace Assimp
#endif // ASSIMP_BUILD_NO_3MF_IMPORTER

View File

@@ -0,0 +1,91 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef AI_D3MFLOADER_H_INCLUDED
#define AI_D3MFLOADER_H_INCLUDED
#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER
#include <assimp/BaseImporter.h>
namespace Assimp {
// ---------------------------------------------------------------------------
/// @brief The 3MF-importer class.
///
/// Implements the basic topology import and embedded textures.
// ---------------------------------------------------------------------------
class D3MFImporter final : public BaseImporter {
public:
/// @brief The default class constructor.
D3MFImporter() = default;
/// @brief The class destructor.
~D3MFImporter() override = default;
/// @brief Performs the data format detection.
/// @param pFile The filename to check.
/// @param pIOHandler The used IO-System.
/// @param checkSig true for signature checking.
/// @return true for can be loaded, false for not.
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
/// @brief Not used
/// @param pImp Not used
void SetupProperties(const Importer *pImp) override;
/// @brief The importer description getter.
/// @return The info
const aiImporterDesc *GetInfo() const override;
protected:
/// @brief Internal read function, performs the file parsing.
/// @param pFile The filename
/// @param pScene The scene to load in.
/// @param pIOHandler The io-system
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
};
} // Namespace Assimp
#endif // #ifndef ASSIMP_BUILD_NO_3MF_IMPORTER
#endif // AI_D3MFLOADER_H_INCLUDED

View File

@@ -0,0 +1,255 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER
#include "D3MFOpcPackage.h"
#include <assimp/Exceptional.h>
#include <assimp/XmlParser.h>
#include <assimp/ZipArchiveIOSystem.h>
#include <assimp/ai_assert.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
#include <assimp/texture.h>
#include "3MFXmlTags.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <map>
#include <vector>
namespace Assimp {
namespace D3MF {
// ------------------------------------------------------------------------------------------------
using OpcPackageRelationshipPtr = std::shared_ptr<OpcPackageRelationship>;
class OpcPackageRelationshipReader final {
public:
explicit OpcPackageRelationshipReader(XmlParser &parser) : mRelations() {
XmlNode root = parser.getRootNode();
ParseRootNode(root);
}
void ParseRootNode(XmlNode &node) {
ParseAttributes(node);
for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
std::string name = currentNode.name();
if (name == "Relationships") {
ParseRelationsNode(currentNode);
}
}
}
void ParseAttributes(XmlNode & /*node*/) {
// empty
}
bool validateRels(OpcPackageRelationshipPtr &relPtr) {
if (relPtr->id.empty() || relPtr->type.empty() || relPtr->target.empty()) {
return false;
}
return true;
}
void ParseRelationsNode(XmlNode &node) {
if (node.empty()) {
return;
}
for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
const std::string name = currentNode.name();
if (name == "Relationship") {
OpcPackageRelationshipPtr relPtr(new OpcPackageRelationship());
relPtr->id = currentNode.attribute(XmlTag::RELS_ATTRIB_ID).as_string();
relPtr->type = currentNode.attribute(XmlTag::RELS_ATTRIB_TYPE).as_string();
relPtr->target = currentNode.attribute(XmlTag::RELS_ATTRIB_TARGET).as_string();
if (validateRels(relPtr)) {
mRelations.push_back(relPtr);
}
}
}
}
std::vector<OpcPackageRelationshipPtr> mRelations;
};
static bool IsEmbeddedTexture( const std::string &filename ) {
const std::string extension = BaseImporter::GetExtension(filename);
if (extension == "jpg" || extension == "png" || extension == "jpeg") {
std::string::size_type pos = filename.find("thumbnail");
if (pos != std::string::npos) {
return false;
}
return true;
}
return false;
}
// ------------------------------------------------------------------------------------------------
D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) :
mRootStream(nullptr),
mZipArchive() {
mZipArchive = new ZipArchiveIOSystem(pIOHandler, rFile);
if (!mZipArchive->isOpen()) {
throw DeadlyImportError("Failed to open file ", rFile, ".");
}
std::vector<std::string> fileList;
mZipArchive->getFileList(fileList);
for (auto &file : fileList) {
if (file == D3MF::XmlTag::ROOT_RELATIONSHIPS_ARCHIVE) {
if (!mZipArchive->Exists(file.c_str())) {
continue;
}
IOStream *fileStream = mZipArchive->Open(file.c_str());
if (nullptr == fileStream) {
ASSIMP_LOG_ERROR("Filestream is nullptr.");
continue;
}
std::string rootFile = ReadPackageRootRelationship(fileStream);
if (!rootFile.empty() && rootFile[0] == '/') {
rootFile = rootFile.substr(1);
if (rootFile[0] == '/') {
// deal with zip-bug
rootFile = rootFile.substr(1);
}
}
ASSIMP_LOG_VERBOSE_DEBUG(rootFile);
mZipArchive->Close(fileStream);
mRootStream = mZipArchive->Open(rootFile.c_str());
ai_assert(mRootStream != nullptr);
if (nullptr == mRootStream) {
throw DeadlyImportError("Cannot open root-file in archive : " + rootFile);
}
} else if (file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
ASSIMP_LOG_WARN("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES", file);
} else if (IsEmbeddedTexture(file)) {
IOStream *fileStream = mZipArchive->Open(file.c_str());
LoadEmbeddedTextures(fileStream, file);
mZipArchive->Close(fileStream);
} else {
ASSIMP_LOG_WARN("Ignored file of unknown type: ", file);
}
}
}
D3MFOpcPackage::~D3MFOpcPackage() {
mZipArchive->Close(mRootStream);
delete mZipArchive;
}
IOStream *D3MFOpcPackage::RootStream() const {
return mRootStream;
}
const std::vector<aiTexture *> &D3MFOpcPackage::GetEmbeddedTextures() const {
return mEmbeddedTextures;
}
static const char *const ModelRef = "3D/3dmodel.model";
bool D3MFOpcPackage::validate() {
if (nullptr == mRootStream || nullptr == mZipArchive) {
return false;
}
return mZipArchive->Exists(ModelRef);
}
std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream *stream) {
XmlParser xmlParser;
if (!xmlParser.parse(stream)) {
return std::string();
}
OpcPackageRelationshipReader reader(xmlParser);
auto itr = std::find_if(reader.mRelations.begin(), reader.mRelations.end(), [](const OpcPackageRelationshipPtr &rel) {
return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
});
if (itr == reader.mRelations.end()) {
throw DeadlyImportError("Cannot find ", XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE);
}
return (*itr)->target;
}
void D3MFOpcPackage::LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename) {
if (nullptr == fileStream) {
return;
}
const size_t size = fileStream->FileSize();
if (0 == size) {
return;
}
unsigned char *data = new unsigned char[size];
fileStream->Read(data, 1, size);
aiTexture *texture = new aiTexture;
std::string embName = "*" + filename;
texture->mFilename.Set(embName.c_str());
texture->mWidth = static_cast<unsigned int>(size);
texture->mHeight = 0;
texture->achFormatHint[0] = 'p';
texture->achFormatHint[1] = 'n';
texture->achFormatHint[2] = 'g';
texture->achFormatHint[3] = '\0';
texture->pcData = (aiTexel*) data;
mEmbeddedTextures.emplace_back(texture);
}
} // Namespace D3MF
} // Namespace Assimp
#endif //ASSIMP_BUILD_NO_3MF_IMPORTER

View File

@@ -0,0 +1,84 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef D3MFOPCPACKAGE_H
#define D3MFOPCPACKAGE_H
#include <assimp/IOSystem.hpp>
#include <memory>
#include <string>
struct aiTexture;
namespace Assimp {
class ZipArchiveIOSystem;
namespace D3MF {
struct OpcPackageRelationship {
std::string id;
std::string type;
std::string target;
};
class D3MFOpcPackage {
public:
D3MFOpcPackage( IOSystem* pIOHandler, const std::string& file );
~D3MFOpcPackage();
IOStream* RootStream() const;
bool validate();
const std::vector<aiTexture*> &GetEmbeddedTextures() const;
protected:
std::string ReadPackageRootRelationship(IOStream* stream);
void LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename);
private:
IOStream* mRootStream;
ZipArchiveIOSystem *mZipArchive;
std::vector<aiTexture *> mEmbeddedTextures;
};
} // namespace D3MF
} // namespace Assimp
#endif // D3MFOPCPACKAGE_H

View File

@@ -0,0 +1,724 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#include "XmlSerializer.h"
#include "D3MFOpcPackage.h"
#include "3MFXmlTags.h"
#include "3MFTypes.h"
#include <assimp/scene.h>
#include <utility>
namespace Assimp {
namespace D3MF {
static constexpr int IdNotSet = -1;
namespace {
static constexpr size_t ColRGBA_Len = 9;
static constexpr size_t ColRGB_Len = 7;
// format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1)
bool validateColorString(const std::string color) {
const size_t len = color.size();
if (ColRGBA_Len != len && ColRGB_Len != len) {
return false;
}
return true;
}
aiFace ReadTriangle(XmlNode &node, int &texId0, int &texId1, int &texId2) {
aiFace face;
face.mNumIndices = 3;
face.mIndices = new unsigned int[face.mNumIndices];
face.mIndices[0] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v1).as_string()));
face.mIndices[1] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v2).as_string()));
face.mIndices[2] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v3).as_string()));
texId0 = texId1 = texId2 = IdNotSet;
XmlParser::getIntAttribute(node, XmlTag::p1, texId0);
XmlParser::getIntAttribute(node, XmlTag::p2, texId1);
XmlParser::getIntAttribute(node, XmlTag::p3, texId2);
return face;
}
aiVector3D ReadVertex(XmlNode &node) {
aiVector3D vertex;
vertex.x = ai_strtof(node.attribute(XmlTag::x).as_string(), nullptr);
vertex.y = ai_strtof(node.attribute(XmlTag::y).as_string(), nullptr);
vertex.z = ai_strtof(node.attribute(XmlTag::z).as_string(), nullptr);
return vertex;
}
bool getNodeAttribute(const XmlNode &node, const std::string &attribute, std::string &value) {
pugi::xml_attribute objectAttribute = node.attribute(attribute.c_str());
if (!objectAttribute.empty()) {
value = objectAttribute.as_string();
return true;
}
return false;
}
bool getNodeAttribute(const XmlNode &node, const std::string &attribute, int &value) {
std::string strValue;
const bool ret = getNodeAttribute(node, attribute, strValue);
if (ret) {
value = std::atoi(strValue.c_str());
return true;
}
return false;
}
aiMatrix4x4 parseTransformMatrix(const std::string& matrixStr) {
// split the string
std::vector<float> numbers;
std::string currentNumber;
for (char c : matrixStr) {
if (c == ' ') {
if (!currentNumber.empty()) {
float f = std::stof(currentNumber);
numbers.push_back(f);
currentNumber.clear();
}
} else {
currentNumber.push_back(c);
}
}
if (!currentNumber.empty()) {
const float f = std::stof(currentNumber);
numbers.push_back(f);
}
aiMatrix4x4 transformMatrix;
transformMatrix.a1 = numbers[0];
transformMatrix.b1 = numbers[1];
transformMatrix.c1 = numbers[2];
transformMatrix.d1 = 0;
transformMatrix.a2 = numbers[3];
transformMatrix.b2 = numbers[4];
transformMatrix.c2 = numbers[5];
transformMatrix.d2 = 0;
transformMatrix.a3 = numbers[6];
transformMatrix.b3 = numbers[7];
transformMatrix.c3 = numbers[8];
transformMatrix.d3 = 0;
transformMatrix.a4 = numbers[9];
transformMatrix.b4 = numbers[10];
transformMatrix.c4 = numbers[11];
transformMatrix.d4 = 1;
return transformMatrix;
}
bool parseColor(const std::string &color, aiColor4D &diffuse) {
if (color.empty()) {
return false;
}
if (!validateColorString(color)) {
return false;
}
if ('#' != color[0]) {
return false;
}
char r[3] = { color[1], color[2], '\0' };
diffuse.r = static_cast<ai_real>(strtol(r, nullptr, 16)) / ai_real(255.0);
char g[3] = { color[3], color[4], '\0' };
diffuse.g = static_cast<ai_real>(strtol(g, nullptr, 16)) / ai_real(255.0);
char b[3] = { color[5], color[6], '\0' };
diffuse.b = static_cast<ai_real>(strtol(b, nullptr, 16)) / ai_real(255.0);
const size_t len = color.size();
if (ColRGB_Len == len) {
return true;
}
char a[3] = { color[7], color[8], '\0' };
diffuse.a = static_cast<ai_real>(strtol(a, nullptr, 16)) / ai_real(255.0);
return true;
}
void assignDiffuseColor(XmlNode &node, aiMaterial *mat) {
const char *color = node.attribute(XmlTag::basematerials_displaycolor).as_string();
aiColor4D diffuse;
if (parseColor(color, diffuse)) {
mat->AddProperty<aiColor4D>(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
}
}
} // namespace
XmlSerializer::XmlSerializer(XmlParser &xmlParser) :
mResourcesDictionnary(),
mMeshCount(0),
mXmlParser(xmlParser) {
// empty
}
XmlSerializer::~XmlSerializer() {
for (auto &it : mResourcesDictionnary) {
delete it.second;
}
}
void XmlSerializer::ImportXml(aiScene *scene) {
if (nullptr == scene) {
return;
}
scene->mRootNode = new aiNode(XmlTag::RootTag);
XmlNode node = mXmlParser.getRootNode().child(XmlTag::model);
if (node.empty()) {
return;
}
XmlNode resNode = node.child(XmlTag::resources);
for (auto &currentNode : resNode.children()) {
const std::string currentNodeName = currentNode.name();
if (currentNodeName == XmlTag::texture_2d) {
ReadEmbeddecTexture(currentNode);
} else if (currentNodeName == XmlTag::texture_group) {
ReadTextureGroup(currentNode);
} else if (currentNodeName == XmlTag::object) {
ReadObject(currentNode);
} else if (currentNodeName == XmlTag::basematerials) {
ReadBaseMaterials(currentNode);
} else if (currentNodeName == XmlTag::meta) {
ReadMetadata(currentNode);
} else if (currentNodeName == XmlTag::colorgroup) {
ReadColorGroup(currentNode);
}
}
StoreMaterialsInScene(scene);
XmlNode buildNode = node.child(XmlTag::build);
if (buildNode.empty()) {
return;
}
for (auto &currentNode : buildNode.children()) {
const std::string currentNodeName = currentNode.name();
if (currentNodeName == XmlTag::item) {
int objectId = IdNotSet;
std::string transformationMatrixStr;
aiMatrix4x4 transformationMatrix;
getNodeAttribute(currentNode, D3MF::XmlTag::objectid, objectId);
bool hasTransform = getNodeAttribute(currentNode, D3MF::XmlTag::transform, transformationMatrixStr);
auto it = mResourcesDictionnary.find(objectId);
if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) {
Object *obj = static_cast<Object *>(it->second);
if (hasTransform) {
transformationMatrix = parseTransformMatrix(transformationMatrixStr);
}
addObjectToNode(scene->mRootNode, obj, transformationMatrix);
}
}
}
// import the metadata
if (!mMetaData.empty()) {
const size_t numMeta = mMetaData.size();
scene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(numMeta));
for (size_t i = 0; i < numMeta; ++i) {
aiString val(mMetaData[i].value);
scene->mMetaData->Set(static_cast<unsigned int>(i), mMetaData[i].name, val);
}
}
// import the meshes, materials are already stored
scene->mNumMeshes = static_cast<unsigned int>(mMeshCount);
if (scene->mNumMeshes != 0) {
scene->mMeshes = new aiMesh *[scene->mNumMeshes]();
for (auto &it : mResourcesDictionnary) {
if (it.second->getType() == ResourceType::RT_Object) {
Object *obj = static_cast<Object *>(it.second);
ai_assert(nullptr != obj);
for (unsigned int i = 0; i < obj->mMeshes.size(); ++i) {
scene->mMeshes[obj->mMeshIndex[i]] = obj->mMeshes[i];
}
}
}
}
}
void XmlSerializer::addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform) {
ai_assert(nullptr != obj);
aiNode *sceneNode = new aiNode(obj->mName);
sceneNode->mNumMeshes = static_cast<unsigned int>(obj->mMeshes.size());
sceneNode->mMeshes = new unsigned int[sceneNode->mNumMeshes];
std::copy(obj->mMeshIndex.begin(), obj->mMeshIndex.end(), sceneNode->mMeshes);
sceneNode->mTransformation = nodeTransform;
if (nullptr != parent) {
parent->addChildren(1, &sceneNode);
}
for (Assimp::D3MF::Component c : obj->mComponents) {
auto it = mResourcesDictionnary.find(c.mObjectId);
if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) {
addObjectToNode(sceneNode, static_cast<Object *>(it->second), c.mTransformation);
}
}
}
void XmlSerializer::ReadObject(XmlNode &node) {
int id = IdNotSet, pid = IdNotSet, pindex = IdNotSet;
bool hasId = getNodeAttribute(node, XmlTag::id, id);
if (!hasId) {
return;
}
bool hasPid = getNodeAttribute(node, XmlTag::pid, pid);
bool hasPindex = getNodeAttribute(node, XmlTag::pindex, pindex);
Object *obj = new Object(id);
for (XmlNode &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == D3MF::XmlTag::mesh) {
auto mesh = ReadMesh(currentNode);
mesh->mName.Set(ai_to_string(id));
if (hasPid) {
auto it = mResourcesDictionnary.find(pid);
if (hasPindex && it != mResourcesDictionnary.end()) {
if (it->second->getType() == ResourceType::RT_BaseMaterials) {
BaseMaterials *materials = static_cast<BaseMaterials *>(it->second);
mesh->mMaterialIndex = materials->mMaterialIndex[pindex];
} else if (it->second->getType() == ResourceType::RT_Texture2DGroup) {
Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second);
if (mesh->mTextureCoords[0] == nullptr) {
mesh->mNumUVComponents[0] = 2;
for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
mesh->mNumUVComponents[i] = 0;
}
const std::string name = ai_to_string(group->mTexId);
for (size_t i = 0; i < mMaterials.size(); ++i) {
if (name == mMaterials[i]->GetName().C_Str()) {
mesh->mMaterialIndex = static_cast<unsigned int>(i);
}
}
mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
mesh->mTextureCoords[0][vertex_idx] =
aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f);
}
} else {
for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
if (mesh->mTextureCoords[0][vertex_idx].z < 0) {
// use default
mesh->mTextureCoords[0][vertex_idx] =
aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f);
}
}
}
}else if (it->second->getType() == ResourceType::RT_ColorGroup) {
if (mesh->mColors[0] == nullptr) {
mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
ColorGroup *group = static_cast<ColorGroup *>(it->second);
for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) {
mesh->mColors[0][vertex_idx] = group->mColors[pindex];
}
}
}
}
}
obj->mMeshes.push_back(mesh);
obj->mMeshIndex.push_back(mMeshCount);
mMeshCount++;
} else if (currentName == D3MF::XmlTag::components) {
for (XmlNode &currentSubNode : currentNode.children()) {
const std::string subNodeName = currentSubNode.name();
if (subNodeName == D3MF::XmlTag::component) {
int objectId = IdNotSet;
std::string componentTransformStr;
aiMatrix4x4 componentTransform;
if (getNodeAttribute(currentSubNode, D3MF::XmlTag::transform, componentTransformStr)) {
componentTransform = parseTransformMatrix(componentTransformStr);
}
if (getNodeAttribute(currentSubNode, D3MF::XmlTag::objectid, objectId)) {
obj->mComponents.push_back({ objectId, componentTransform });
}
}
}
}
}
mResourcesDictionnary.insert(std::make_pair(id, obj));
}
aiMesh *XmlSerializer::ReadMesh(XmlNode &node) {
if (node.empty()) {
return nullptr;
}
aiMesh *mesh = new aiMesh();
for (XmlNode &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == XmlTag::vertices) {
ImportVertices(currentNode, mesh);
} else if (currentName == XmlTag::triangles) {
ImportTriangles(currentNode, mesh);
}
}
return mesh;
}
void XmlSerializer::ReadMetadata(XmlNode &node) {
pugi::xml_attribute attribute = node.attribute(D3MF::XmlTag::meta_name);
const std::string name = attribute.as_string();
const std::string value = node.value();
if (name.empty()) {
return;
}
MetaEntry entry;
entry.name = name;
entry.value = value;
mMetaData.push_back(entry);
}
void XmlSerializer::ImportVertices(XmlNode &node, aiMesh *mesh) {
ai_assert(nullptr != mesh);
std::vector<aiVector3D> vertices;
for (XmlNode &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == XmlTag::vertex) {
vertices.push_back(ReadVertex(currentNode));
}
}
mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
mesh->mVertices = new aiVector3D[mesh->mNumVertices];
std::copy(vertices.begin(), vertices.end(), mesh->mVertices);
}
void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
std::vector<aiFace> faces;
for (XmlNode &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == XmlTag::triangle) {
int pid = IdNotSet;
bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid);
int pindex[3];
aiFace face = ReadTriangle(currentNode, pindex[0], pindex[1], pindex[2]);
if (hasPid && (pindex[0] != IdNotSet || pindex[1] != IdNotSet || pindex[2] != IdNotSet)) {
auto it = mResourcesDictionnary.find(pid);
if (it != mResourcesDictionnary.end()) {
if (it->second->getType() == ResourceType::RT_BaseMaterials) {
BaseMaterials *baseMaterials = static_cast<BaseMaterials *>(it->second);
auto update_material = [&](int idx) {
if (pindex[idx] != IdNotSet) {
mesh->mMaterialIndex = baseMaterials->mMaterialIndex[pindex[idx]];
}
};
update_material(0);
update_material(1);
update_material(2);
} else if (it->second->getType() == ResourceType::RT_Texture2DGroup) {
// Load texture coordinates into mesh, when any
Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second); // fix bug
if (mesh->mTextureCoords[0] == nullptr) {
mesh->mNumUVComponents[0] = 2;
for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
mesh->mNumUVComponents[i] = 0;
}
const std::string name = ai_to_string(group->mTexId);
for (size_t i = 0; i < mMaterials.size(); ++i) {
if (name == mMaterials[i]->GetName().C_Str()) {
mesh->mMaterialIndex = static_cast<unsigned int>(i);
}
}
mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
for (unsigned int vertex_index = 0; vertex_index < mesh->mNumVertices; vertex_index++) {
mesh->mTextureCoords[0][vertex_index].z = IdNotSet;//mark not set
}
}
auto update_texture = [&](int idx) {
if (pindex[idx] != IdNotSet) {
size_t vertex_index = face.mIndices[idx];
mesh->mTextureCoords[0][vertex_index] =
aiVector3D(group->mTex2dCoords[pindex[idx]].x, group->mTex2dCoords[pindex[idx]].y, 0.0f);
}
};
update_texture(0);
update_texture(1);
update_texture(2);
} else if (it->second->getType() == ResourceType::RT_ColorGroup) {
// Load vertex color into mesh, when any
ColorGroup *group = static_cast<ColorGroup *>(it->second);
if (mesh->mColors[0] == nullptr) {
mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
}
auto update_color = [&](int idx) {
if (pindex[idx] != IdNotSet) {
size_t vertex_index = face.mIndices[idx];
mesh->mColors[0][vertex_index] = group->mColors[pindex[idx]];
}
};
update_color(0);
update_color(1);
update_color(2);
}
}
}
faces.push_back(face);
}
}
mesh->mNumFaces = static_cast<unsigned int>(faces.size());
mesh->mFaces = new aiFace[mesh->mNumFaces];
mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
std::copy(faces.begin(), faces.end(), mesh->mFaces);
}
void XmlSerializer::ReadBaseMaterials(XmlNode &node) {
int id = IdNotSet;
if (getNodeAttribute(node, D3MF::XmlTag::id, id)) {
BaseMaterials *baseMaterials = new BaseMaterials(id);
for (XmlNode &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == XmlTag::basematerials_base) {
baseMaterials->mMaterialIndex.push_back(static_cast<unsigned int>(mMaterials.size()));
mMaterials.push_back(readMaterialDef(currentNode, id));
}
}
mResourcesDictionnary.insert(std::make_pair(id, baseMaterials));
}
}
void XmlSerializer::ReadEmbeddecTexture(XmlNode &node) {
if (node.empty()) {
return;
}
std::string value;
EmbeddedTexture *tex2D = nullptr;
if (XmlParser::getStdStrAttribute(node, XmlTag::id, value)) {
tex2D = new EmbeddedTexture(atoi(value.c_str()));
}
if (nullptr == tex2D) {
return;
}
if (XmlParser::getStdStrAttribute(node, XmlTag::path, value)) {
tex2D->mPath = value;
}
if (XmlParser::getStdStrAttribute(node, XmlTag::texture_content_type, value)) {
tex2D->mContentType = value;
}
if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestyleu, value)) {
tex2D->mTilestyleU = value;
}
if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestylev, value)) {
tex2D->mTilestyleV = value;
}
mEmbeddedTextures.emplace_back(tex2D);
StoreEmbeddedTexture(tex2D);
}
void XmlSerializer::StoreEmbeddedTexture(EmbeddedTexture *tex) {
aiMaterial *mat = new aiMaterial;
aiString s;
s.Set(ai_to_string(tex->mId).c_str());
mat->AddProperty(&s, AI_MATKEY_NAME);
const std::string name = "*" + tex->mPath;
s.Set(name);
mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
aiColor3D col;
mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_DIFFUSE);
mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_AMBIENT);
mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_EMISSIVE);
mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_SPECULAR);
mMaterials.emplace_back(mat);
}
void XmlSerializer::ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup) {
if (node.empty() || nullptr == tex2DGroup) {
return;
}
int id = IdNotSet;
if (XmlParser::getIntAttribute(node, "texid", id)) {
tex2DGroup->mTexId = id;
}
double value = 0.0;
for (XmlNode currentNode : node.children()) {
const std::string currentName = currentNode.name();
aiVector2D texCoord;
if (currentName == XmlTag::texture_2d_coord) {
XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_u, value);
texCoord.x = (ai_real)value;
XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_v, value);
texCoord.y = (ai_real)value;
tex2DGroup->mTex2dCoords.push_back(texCoord);
}
}
}
void XmlSerializer::ReadTextureGroup(XmlNode &node) {
if (node.empty()) {
return;
}
int id = IdNotSet;
if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) {
return;
}
Texture2DGroup *group = new Texture2DGroup(id);
ReadTextureCoords2D(node, group);
mResourcesDictionnary.insert(std::make_pair(id, group));
}
aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basematerialsId) {
aiMaterial *material = new aiMaterial();
material->mNumProperties = 0;
std::string name;
bool hasName = getNodeAttribute(node, D3MF::XmlTag::basematerials_name, name);
std::string stdMaterialName;
const std::string strId(ai_to_string(basematerialsId));
stdMaterialName += "id";
stdMaterialName += strId;
stdMaterialName += "_";
if (hasName) {
stdMaterialName += name;
} else {
stdMaterialName += "basemat_";
stdMaterialName += ai_to_string(mMaterials.size());
}
aiString assimpMaterialName(stdMaterialName);
material->AddProperty(&assimpMaterialName, AI_MATKEY_NAME);
assignDiffuseColor(node, material);
return material;
}
void XmlSerializer::ReadColor(XmlNode &node, ColorGroup *colorGroup) {
if (node.empty() || nullptr == colorGroup) {
return;
}
for (XmlNode currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == XmlTag::color_item) {
const char *color = currentNode.attribute(XmlTag::color_value).as_string();
aiColor4D color_value;
if (parseColor(color, color_value)) {
colorGroup->mColors.push_back(color_value);
}
}
}
}
void XmlSerializer::ReadColorGroup(XmlNode &node) {
if (node.empty()) {
return;
}
int id = IdNotSet;
if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) {
return;
}
ColorGroup *group = new ColorGroup(id);
ReadColor(node, group);
mResourcesDictionnary.insert(std::make_pair(id, group));
}
void XmlSerializer::StoreMaterialsInScene(aiScene *scene) {
if (nullptr == scene) {
return;
}
scene->mNumMaterials = static_cast<unsigned int>(mMaterials.size());
if (scene->mNumMaterials == 0) {
return;
}
scene->mMaterials = new aiMaterial *[scene->mNumMaterials];
for (size_t i = 0; i < mMaterials.size(); ++i) {
scene->mMaterials[i] = mMaterials[i];
}
}
} // namespace D3MF
} // namespace Assimp

View File

@@ -0,0 +1,100 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#pragma once
#include <assimp/XmlParser.h>
#include <assimp/mesh.h>
#include <vector>
#include <map>
struct aiNode;
struct aiMesh;
struct aiMaterial;
namespace Assimp {
namespace D3MF {
class Resource;
class D3MFOpcPackage;
class Object;
class Texture2DGroup;
class EmbeddedTexture;
class ColorGroup;
/// @brief his class implements ther 3mf serialization.
class XmlSerializer final {
public:
explicit XmlSerializer(XmlParser &xmlParser);
~XmlSerializer();
void ImportXml(aiScene *scene);
private:
void addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform);
void ReadObject(XmlNode &node);
aiMesh *ReadMesh(XmlNode &node);
void ReadMetadata(XmlNode &node);
void ImportVertices(XmlNode &node, aiMesh *mesh);
void ImportTriangles(XmlNode &node, aiMesh *mesh);
void ReadBaseMaterials(XmlNode &node);
void ReadEmbeddecTexture(XmlNode &node);
void StoreEmbeddedTexture(EmbeddedTexture *tex);
void ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup);
void ReadTextureGroup(XmlNode &node);
aiMaterial *readMaterialDef(XmlNode &node, unsigned int basematerialsId);
void StoreMaterialsInScene(aiScene *scene);
void ReadColorGroup(XmlNode &node);
void ReadColor(XmlNode &node, ColorGroup *colorGroup);
private:
struct MetaEntry {
std::string name;
std::string value;
};
std::vector<MetaEntry> mMetaData;
std::vector<EmbeddedTexture *> mEmbeddedTextures;
std::vector<aiMaterial *> mMaterials;
std::map<unsigned int, Resource *> mResourcesDictionnary;
unsigned int mMeshCount;
XmlParser &mXmlParser;
};
} // namespace D3MF
} // namespace Assimp

View File

@@ -0,0 +1,896 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/** @file Implementation of the AC3D importer class */
#ifndef ASSIMP_BUILD_NO_AC_IMPORTER
// internal headers
#include "ACLoader.h"
#include "Common/Importer.h"
#include <assimp/BaseImporter.h>
#include <assimp/ParsingUtils.h>
#include <assimp/Subdivision.h>
#include <assimp/config.h>
#include <assimp/fast_atof.h>
#include <assimp/importerdesc.h>
#include <assimp/light.h>
#include <assimp/material.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/IOSystem.hpp>
#include <assimp/Importer.hpp>
#include <memory>
namespace Assimp {
static constexpr aiImporterDesc desc = {
"AC3D Importer",
"",
"",
"",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"ac acc ac3d"
};
static constexpr auto ACDoubleSidedFlag = 0x20;
// ------------------------------------------------------------------------------------------------
// skip to the next token
inline const char *AcSkipToNextToken(const char *buffer, const char *end) {
if (!SkipSpaces(&buffer, end)) {
ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL");
}
return buffer;
}
// ------------------------------------------------------------------------------------------------
// read a string (may be enclosed in double quotation marks). buffer must point to "
inline const char *AcGetString(const char *buffer, const char *end, std::string &out) {
if (*buffer == '\0') {
throw DeadlyImportError("AC3D: Unexpected EOF in string");
}
++buffer;
const char *sz = buffer;
while ('\"' != *buffer && buffer != end) {
if (IsLineEnd(*buffer)) {
ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string");
out = "ERROR";
break;
}
++buffer;
}
if (IsLineEnd(*buffer)) {
return buffer;
}
out = std::string(sz, (unsigned int)(buffer - sz));
++buffer;
return buffer;
}
// ------------------------------------------------------------------------------------------------
// read 1 to n floats prefixed with an optional predefined identifier
template <class T>
inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) {
buffer = AcSkipToNextToken(buffer, end);
if (0 != name_length) {
if (0 != strncmp(buffer, name, name_length) || !IsSpace(buffer[name_length])) {
ASSIMP_LOG_ERROR("AC3D: Unexpected token. ", name, " was expected.");
return buffer;
}
buffer += name_length + 1;
}
for (unsigned int _i = 0; _i < num; ++_i) {
buffer = AcSkipToNextToken(buffer, end);
buffer = fast_atoreal_move(buffer, ((float *)out)[_i]);
}
return buffer;
}
// ------------------------------------------------------------------------------------------------
// Reverses vertex indices in a face.
static void flipWindingOrder(aiFace &f) {
std::reverse(f.mIndices, f.mIndices + f.mNumIndices);
}
// ------------------------------------------------------------------------------------------------
// Duplicates a face and inverts it. Also duplicates all vertices (so the new face gets its own
// set of normals and isnt smoothed against the original).
static void buildBacksideOfFace(const aiFace &origFace, aiFace *&outFaces, aiVector3D *&outVertices, const aiVector3D *allVertices,
aiVector3D *&outUV, const aiVector3D *allUV, unsigned &curIdx) {
auto &newFace = *outFaces++;
newFace = origFace;
flipWindingOrder(newFace);
for (unsigned f = 0; f < newFace.mNumIndices; ++f) {
*outVertices++ = allVertices[newFace.mIndices[f]];
if (outUV) {
*outUV = allUV[newFace.mIndices[f]];
outUV++;
}
newFace.mIndices[f] = curIdx++;
}
}
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
AC3DImporter::AC3DImporter() :
mBuffer(),
configSplitBFCull(),
configEvalSubdivision(),
mNumMeshes(),
mLights(),
mLightsCounter(0),
mGroupsCounter(0),
mPolysCounter(0),
mWorldsCounter(0) {
// nothing to be done here
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
static constexpr uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") };
return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
}
// ------------------------------------------------------------------------------------------------
// Loader meta information
const aiImporterDesc *AC3DImporter::GetInfo() const {
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Get a pointer to the next line from the file
bool AC3DImporter::GetNextLine() {
SkipLine(&mBuffer.data, mBuffer.end);
return SkipSpaces(&mBuffer.data, mBuffer.end);
}
// ------------------------------------------------------------------------------------------------
// Parse an object section in an AC file
bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
return false;
}
SkipSpaces(&mBuffer.data, mBuffer.end);
++mNumMeshes;
objects.emplace_back();
Object &obj = objects.back();
aiLight *light = nullptr;
if (!ASSIMP_strincmp(mBuffer.data, "light", 5)) {
// This is a light source. Add it to the list
mLights->push_back(light = new aiLight());
// Return a point light with no attenuation
light->mType = aiLightSource_POINT;
light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f, 1.f, 1.f);
light->mAttenuationConstant = 1.f;
// Generate a default name for both the light source and the node
light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast<unsigned int>(mLights->size()) - 1);
obj.name = std::string(light->mName.data);
ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered");
obj.type = Object::Light;
} else if (!ASSIMP_strincmp(mBuffer.data, "group", 5)) {
obj.type = Object::Group;
} else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
obj.type = Object::World;
} else {
obj.type = Object::Poly;
}
while (GetNextLine()) {
if (TokenMatch(mBuffer.data, "kids", 4)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
unsigned int num = strtoul10(mBuffer.data, &mBuffer.data);
GetNextLine();
if (num) {
// load the children of this object recursively
obj.children.reserve(num);
for (unsigned int i = 0; i < num; ++i) {
if (!LoadObjectSection(obj.children)) {
ASSIMP_LOG_WARN("AC3D: wrong number of kids");
break;
}
}
}
return true;
} else if (TokenMatch(mBuffer.data, "name", 4)) {
SkipSpaces(&mBuffer.data, mBuffer.data);
mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, obj.name);
// If this is a light source, we'll also need to store
// the name of the node in it.
if (light) {
light->mName.Set(obj.name);
}
} else if (TokenMatch(mBuffer.data, "texture", 7)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
std::string texture;
mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, texture);
obj.textures.push_back(texture);
} else if (TokenMatch(mBuffer.data, "texrep", 6)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texRepeat);
if (!obj.texRepeat.x || !obj.texRepeat.y)
obj.texRepeat = aiVector2D(1.f, 1.f);
} else if (TokenMatch(mBuffer.data, "texoff", 6)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texOffset);
} else if (TokenMatch(mBuffer.data, "rot", 3)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 9, &obj.rotation);
} else if (TokenMatch(mBuffer.data, "loc", 3)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &obj.translation);
} else if (TokenMatch(mBuffer.data, "subdiv", 6)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
obj.subDiv = strtoul10(mBuffer.data, &mBuffer.data);
} else if (TokenMatch(mBuffer.data, "crease", 6)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
obj.crease = fast_atof(mBuffer.data);
} else if (TokenMatch(mBuffer.data, "numvert", 7)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
if (t >= AI_MAX_ALLOC(aiVector3D)) {
throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
}
obj.vertices.reserve(t);
for (unsigned int i = 0; i < t; ++i) {
if (!GetNextLine()) {
ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet");
break;
} else if (!IsNumeric(*mBuffer.data)) {
ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet");
--mBuffer.data; // make sure the line is processed a second time
break;
}
obj.vertices.emplace_back();
aiVector3D &v = obj.vertices.back();
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &v.x);
}
} else if (TokenMatch(mBuffer.data, "numsurf", 7)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
bool Q3DWorkAround = false;
const unsigned int t = strtoul10(mBuffer.data, &mBuffer.data);
obj.surfaces.reserve(t);
for (unsigned int i = 0; i < t; ++i) {
GetNextLine();
if (!TokenMatch(mBuffer.data, "SURF", 4)) {
// FIX: this can occur for some files - Quick 3D for
// example writes no surf chunks
if (!Q3DWorkAround) {
ASSIMP_LOG_WARN("AC3D: SURF token was expected");
ASSIMP_LOG_VERBOSE_DEBUG("Continuing with Quick3D Workaround enabled");
}
--mBuffer.data; // make sure the line is processed a second time
// break; --- see fix notes above
Q3DWorkAround = true;
}
SkipSpaces(&mBuffer.data, mBuffer.end);
obj.surfaces.emplace_back();
Surface &surf = obj.surfaces.back();
surf.flags = strtoul_cppstyle(mBuffer.data);
while (true) {
if (!GetNextLine()) {
throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete");
}
if (TokenMatch(mBuffer.data, "mat", 3)) {
SkipSpaces(&mBuffer.data, mBuffer.end);
surf.mat = strtoul10(mBuffer.data);
} else if (TokenMatch(mBuffer.data, "refs", 4)) {
// --- see fix notes above
if (Q3DWorkAround) {
if (!surf.entries.empty()) {
mBuffer.data -= 6;
break;
}
}
SkipSpaces(&mBuffer.data, mBuffer.end);
const unsigned int m = strtoul10(mBuffer.data);
surf.entries.reserve(m);
obj.numRefs += m;
for (unsigned int k = 0; k < m; ++k) {
if (!GetNextLine()) {
ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete");
break;
}
surf.entries.emplace_back();
Surface::SurfaceEntry &entry = surf.entries.back();
entry.first = strtoul10(mBuffer.data, &mBuffer.data);
SkipSpaces(&mBuffer.data, mBuffer.end);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &entry.second);
}
} else {
--mBuffer.data; // make sure the line is processed a second time
break;
}
}
}
}
}
ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
return false;
}
// ------------------------------------------------------------------------------------------------
// Convert a material from AC3DImporter::Material to aiMaterial
void AC3DImporter::ConvertMaterial(const Object &object,
const Material &matSrc,
aiMaterial &matDest) {
aiString s;
if (matSrc.name.length()) {
s.Set(matSrc.name);
matDest.AddProperty(&s, AI_MATKEY_NAME);
}
if (!object.textures.empty()) {
s.Set(object.textures[0]);
matDest.AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
// UV transformation
if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y ||
object.texOffset.x || object.texOffset.y) {
aiUVTransform transform;
transform.mScaling = object.texRepeat;
transform.mTranslation = object.texOffset;
matDest.AddProperty(&transform, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0));
}
}
matDest.AddProperty<aiColor3D>(&matSrc.rgb, 1, AI_MATKEY_COLOR_DIFFUSE);
matDest.AddProperty<aiColor3D>(&matSrc.amb, 1, AI_MATKEY_COLOR_AMBIENT);
matDest.AddProperty<aiColor3D>(&matSrc.emis, 1, AI_MATKEY_COLOR_EMISSIVE);
matDest.AddProperty<aiColor3D>(&matSrc.spec, 1, AI_MATKEY_COLOR_SPECULAR);
int n = -1;
if (matSrc.shin) {
n = aiShadingMode_Phong;
matDest.AddProperty<float>(&matSrc.shin, 1, AI_MATKEY_SHININESS);
} else {
n = aiShadingMode_Gouraud;
}
matDest.AddProperty<int>(&n, 1, AI_MATKEY_SHADING_MODEL);
float f = 1.f - matSrc.trans;
matDest.AddProperty<float>(&f, 1, AI_MATKEY_OPACITY);
}
// ------------------------------------------------------------------------------------------------
// Converts the loaded data to the internal verbose representation
aiNode *AC3DImporter::ConvertObjectSection(Object &object,
std::vector<aiMesh *> &meshes,
std::vector<aiMaterial *> &outMaterials,
const std::vector<Material> &materials,
aiNode *parent) {
aiNode *node = new aiNode();
node->mParent = parent;
if (object.vertices.size()) {
if (!object.surfaces.size() || !object.numRefs) {
/* " An object with 7 vertices (no surfaces, no materials defined).
This is a good way of getting point data into AC3D.
The Vertex->create convex-surface/object can be used on these
vertices to 'wrap' a 3d shape around them "
(http://www.opencity.info/html/ac3dfileformat.html)
therefore: if no surfaces are defined return point data only
*/
ASSIMP_LOG_INFO("AC3D: No surfaces defined in object definition, "
"a point list is returned");
meshes.push_back(new aiMesh());
aiMesh *mesh = meshes.back();
mesh->mNumFaces = mesh->mNumVertices = (unsigned int)object.vertices.size();
aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
aiVector3D *verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
for (unsigned int i = 0; i < mesh->mNumVertices; ++i, ++faces, ++verts) {
*verts = object.vertices[i];
faces->mNumIndices = 1;
faces->mIndices = new unsigned int[1];
faces->mIndices[0] = i;
}
// use the primary material in this case. this should be the
// default material if all objects of the file contain points
// and no faces.
mesh->mMaterialIndex = 0;
outMaterials.push_back(new aiMaterial());
ConvertMaterial(object, materials[0], *outMaterials.back());
} else {
// need to generate one or more meshes for this object.
// find out how many different materials we have
typedef std::pair<unsigned int, unsigned int> IntPair;
typedef std::vector<IntPair> MatTable;
MatTable needMat(materials.size(), IntPair(0, 0));
std::vector<Surface>::iterator it, end = object.surfaces.end();
std::vector<Surface::SurfaceEntry>::iterator it2, end2;
for (it = object.surfaces.begin(); it != end; ++it) {
unsigned int idx = (*it).mat;
if (idx >= needMat.size()) {
ASSIMP_LOG_ERROR("AC3D: material index is out of range");
idx = 0;
}
if ((*it).entries.empty()) {
ASSIMP_LOG_WARN("AC3D: surface has zero vertex references");
}
const bool isDoubleSided = ACDoubleSidedFlag == (it->flags & ACDoubleSidedFlag);
const int doubleSidedFactor = isDoubleSided ? 2 : 1;
// validate all vertex indices to make sure we won't crash here
for (it2 = (*it).entries.begin(),
end2 = (*it).entries.end();
it2 != end2; ++it2) {
if ((*it2).first >= object.vertices.size()) {
ASSIMP_LOG_WARN("AC3D: Invalid vertex reference");
(*it2).first = 0;
}
}
if (!needMat[idx].first) {
++node->mNumMeshes;
}
switch ((*it).GetType()) {
case Surface::ClosedLine: // closed line
needMat[idx].first += static_cast<unsigned int>((*it).entries.size());
needMat[idx].second += static_cast<unsigned int>((*it).entries.size() << 1u);
break;
// unclosed line
case Surface::OpenLine:
needMat[idx].first += static_cast<unsigned int>((*it).entries.size() - 1);
needMat[idx].second += static_cast<unsigned int>(((*it).entries.size() - 1) << 1u);
break;
// triangle strip
case Surface::TriangleStrip:
needMat[idx].first += static_cast<unsigned int>(it->entries.size() - 2) * doubleSidedFactor;
needMat[idx].second += static_cast<unsigned int>(it->entries.size() - 2) * 3 * doubleSidedFactor;
break;
default:
// Coerce unknowns to a polygon and warn
ASSIMP_LOG_WARN("AC3D: The type flag of a surface is unknown: ", (*it).flags);
(*it).flags &= ~(Surface::Mask);
// fallthrough
// polygon
case Surface::Polygon:
// the number of faces increments by one, the number
// of vertices by surface.numref.
needMat[idx].first += doubleSidedFactor;
needMat[idx].second += static_cast<unsigned int>(it->entries.size()) * doubleSidedFactor;
};
}
unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes];
unsigned int mat = 0;
const size_t oldm = meshes.size();
for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end();
cit != cend; ++cit, ++mat) {
if (!(*cit).first) {
continue;
}
// allocate a new aiMesh object
*pip++ = (unsigned int)meshes.size();
aiMesh *mesh = new aiMesh();
meshes.push_back(mesh);
mesh->mMaterialIndex = static_cast<unsigned int>(outMaterials.size());
outMaterials.push_back(new aiMaterial());
ConvertMaterial(object, materials[mat], *outMaterials.back());
// allocate storage for vertices and normals
mesh->mNumFaces = (*cit).first;
if (mesh->mNumFaces == 0) {
throw DeadlyImportError("AC3D: No faces");
} else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) {
throw DeadlyImportError("AC3D: Too many faces, would run out of memory");
}
aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
mesh->mNumVertices = (*cit).second;
if (mesh->mNumVertices == 0) {
throw DeadlyImportError("AC3D: No vertices");
} else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) {
throw DeadlyImportError("AC3D: Too many vertices, would run out of memory");
}
aiVector3D *vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
unsigned int cur = 0;
// allocate UV coordinates, but only if the texture name for the
// surface is not empty
aiVector3D *uv = nullptr;
if (!object.textures.empty()) {
uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
mesh->mNumUVComponents[0] = 2;
}
for (it = object.surfaces.begin(); it != end; ++it) {
if (mat == (*it).mat) {
const Surface &src = *it;
const bool isDoubleSided = ACDoubleSidedFlag == (src.flags & ACDoubleSidedFlag);
// closed polygon
uint8_t type = (*it).GetType();
if (type == Surface::Polygon) {
aiFace &face = *faces++;
face.mNumIndices = (unsigned int)src.entries.size();
if (0 != face.mNumIndices) {
face.mIndices = new unsigned int[face.mNumIndices];
for (unsigned int i = 0; i < face.mNumIndices; ++i, ++vertices) {
const Surface::SurfaceEntry &entry = src.entries[i];
face.mIndices[i] = cur++;
// copy vertex positions
if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
throw DeadlyImportError("AC3D: Invalid number of vertices");
}
*vertices = object.vertices[entry.first] + object.translation;
// copy texture coordinates
if (uv) {
uv->x = entry.second.x;
uv->y = entry.second.y;
++uv;
}
}
if(isDoubleSided) // Need a backface?
buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
}
} else if (type == Surface::TriangleStrip) {
for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) {
const Surface::SurfaceEntry &entry1 = src.entries[i];
const Surface::SurfaceEntry &entry2 = src.entries[i + 1];
const Surface::SurfaceEntry &entry3 = src.entries[i + 2];
aiFace &face = *faces++;
face.mNumIndices = 3;
face.mIndices = new unsigned int[face.mNumIndices];
face.mIndices[0] = cur++;
face.mIndices[1] = cur++;
face.mIndices[2] = cur++;
if (!(i & 1)) {
*vertices++ = object.vertices[entry1.first] + object.translation;
if (uv) {
uv->x = entry1.second.x;
uv->y = entry1.second.y;
++uv;
}
*vertices++ = object.vertices[entry2.first] + object.translation;
if (uv) {
uv->x = entry2.second.x;
uv->y = entry2.second.y;
++uv;
}
} else {
*vertices++ = object.vertices[entry2.first] + object.translation;
if (uv) {
uv->x = entry2.second.x;
uv->y = entry2.second.y;
++uv;
}
*vertices++ = object.vertices[entry1.first] + object.translation;
if (uv) {
uv->x = entry1.second.x;
uv->y = entry1.second.y;
++uv;
}
}
if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) {
throw DeadlyImportError("AC3D: Invalid number of vertices");
}
*vertices++ = object.vertices[entry3.first] + object.translation;
if (uv) {
uv->x = entry3.second.x;
uv->y = entry3.second.y;
++uv;
}
if(isDoubleSided) // Need a backface?
buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur);
}
} else {
it2 = (*it).entries.begin();
// either a closed or an unclosed line
unsigned int tmp = (unsigned int)(*it).entries.size();
if (Surface::OpenLine == type) --tmp;
for (unsigned int m = 0; m < tmp; ++m) {
aiFace &face = *faces++;
face.mNumIndices = 2;
face.mIndices = new unsigned int[2];
face.mIndices[0] = cur++;
face.mIndices[1] = cur++;
// copy vertex positions
if (it2 == (*it).entries.end()) {
throw DeadlyImportError("AC3D: Bad line");
}
ai_assert((*it2).first < object.vertices.size());
*vertices++ = object.vertices[(*it2).first];
// copy texture coordinates
if (uv) {
uv->x = (*it2).second.x;
uv->y = (*it2).second.y;
++uv;
}
if (Surface::ClosedLine == type && tmp - 1 == m) {
// if this is a closed line repeat its beginning now
it2 = (*it).entries.begin();
} else
++it2;
// second point
*vertices++ = object.vertices[(*it2).first];
if (uv) {
uv->x = (*it2).second.x;
uv->y = (*it2).second.y;
++uv;
}
}
}
}
}
}
// Now apply catmull clark subdivision if necessary. We split meshes into
// materials which is not done by AC3D during smoothing, so we need to
// collect all meshes using the same material group.
if (object.subDiv) {
if (configEvalSubdivision) {
std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE));
ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: ", object.name);
std::vector<aiMesh *> cpy(meshes.size() - oldm, nullptr);
div->Subdivide(&meshes[oldm], cpy.size(), &cpy.front(), object.subDiv, true);
std::copy(cpy.begin(), cpy.end(), meshes.begin() + oldm);
// previous meshes are deleted vy Subdivide().
} else {
ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: ", object.name);
}
}
}
}
if (object.name.length())
node->mName.Set(object.name);
else {
// generate a name depending on the type of the node
switch (object.type) {
case Object::Group:
node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACGroup_%i", mGroupsCounter++);
break;
case Object::Poly:
node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACPoly_%i", mPolysCounter++);
break;
case Object::Light:
node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACLight_%i", mLightsCounter++);
break;
// there shouldn't be more than one world, but we don't care
case Object::World:
node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++);
break;
}
}
// setup the local transformation matrix of the object
// compute the transformation offset to the parent node
node->mTransformation = aiMatrix4x4(object.rotation);
if (object.type == Object::Group || !object.numRefs) {
node->mTransformation.a4 = object.translation.x;
node->mTransformation.b4 = object.translation.y;
node->mTransformation.c4 = object.translation.z;
}
// add children to the object
if (object.children.size()) {
node->mNumChildren = (unsigned int)object.children.size();
node->mChildren = new aiNode *[node->mNumChildren];
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
node->mChildren[i] = ConvertObjectSection(object.children[i], meshes, outMaterials, materials, node);
}
}
return node;
}
// ------------------------------------------------------------------------------------------------
void AC3DImporter::SetupProperties(const Importer *pImp) {
configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL, 1) ? true : false;
configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION, 1) ? true : false;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void AC3DImporter::InternReadFile(const std::string &pFile,
aiScene *pScene, IOSystem *pIOHandler) {
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
// Check whether we can read from the file
if (file == nullptr) {
throw DeadlyImportError("Failed to open AC3D file ", pFile, ".");
}
// allocate storage and copy the contents of the file to a memory buffer
std::vector<char> mBuffer2;
TextFileToBuffer(file.get(), mBuffer2);
mBuffer.data = &mBuffer2[0];
mBuffer.end = &mBuffer2[0] + mBuffer2.size();
mNumMeshes = 0;
mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0;
if (::strncmp(mBuffer.data, "AC3D", 4)) {
throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found");
}
// print the file format version to the console
unsigned int version = HexDigitToDecimal(mBuffer.data[4]);
char msg[3];
ASSIMP_itoa10(msg, 3, version);
ASSIMP_LOG_INFO("AC3D file format version: ", msg);
std::vector<Material> materials;
materials.reserve(5);
std::vector<Object> rootObjects;
rootObjects.reserve(5);
std::vector<aiLight *> lights;
mLights = &lights;
while (GetNextLine()) {
if (TokenMatch(mBuffer.data, "MATERIAL", 8)) {
materials.emplace_back();
Material &mat = materials.back();
// manually parse the material ... sscanf would use the buldin atof ...
// Format: (name) rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f
mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
if ('\"' == *mBuffer.data) {
mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, mat.name);
mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end);
}
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "rgb", 3, 3, &mat.rgb);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "amb", 3, 3, &mat.amb);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "emis", 4, 3, &mat.emis);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "spec", 4, 3, &mat.spec);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "shi", 3, 1, &mat.shin);
mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "trans", 5, 1, &mat.trans);
} else {
LoadObjectSection(rootObjects);
}
}
if (rootObjects.empty() || mNumMeshes == 0u) {
throw DeadlyImportError("AC3D: No meshes have been loaded");
}
if (materials.empty()) {
ASSIMP_LOG_WARN("AC3D: No material has been found");
materials.emplace_back();
}
mNumMeshes += (mNumMeshes >> 2u) + 1;
std::vector<aiMesh *> meshes;
meshes.reserve(mNumMeshes);
std::vector<aiMaterial *> omaterials;
materials.reserve(mNumMeshes);
// generate a dummy root if there are multiple objects on the top layer
Object *root = nullptr;
if (1 == rootObjects.size())
root = &rootObjects[0];
else {
root = new Object();
}
// now convert the imported stuff to our output data structure
pScene->mRootNode = ConvertObjectSection(*root, meshes, omaterials, materials);
if (1 != rootObjects.size()) {
delete root;
}
if (::strncmp(pScene->mRootNode->mName.data, "Node", 4) == 0) {
pScene->mRootNode->mName.Set("<AC3DWorld>");
}
// copy meshes
if (meshes.empty()) {
throw DeadlyImportError("An unknown error occurred during converting");
}
pScene->mNumMeshes = (unsigned int)meshes.size();
pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
::memcpy(pScene->mMeshes, &meshes[0], pScene->mNumMeshes * sizeof(void *));
// copy materials
pScene->mNumMaterials = (unsigned int)omaterials.size();
pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
::memcpy(pScene->mMaterials, &omaterials[0], pScene->mNumMaterials * sizeof(void *));
// copy lights
pScene->mNumLights = (unsigned int)lights.size();
if (!lights.empty()) {
pScene->mLights = new aiLight *[lights.size()];
::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *));
}
}
} // namespace Assimp
#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER

View File

@@ -0,0 +1,270 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file ACLoader.h
* @brief Declaration of the .ac importer class.
*/
#ifndef AI_AC3DLOADER_H_INCLUDED
#define AI_AC3DLOADER_H_INCLUDED
#include <vector>
#include <assimp/BaseImporter.h>
#include <assimp/types.h>
struct aiNode;
struct aiMesh;
struct aiMaterial;
struct aiLight;
namespace Assimp {
// ---------------------------------------------------------------------------
/** AC3D (*.ac) importer class
*/
class AC3DImporter final : public BaseImporter {
public:
AC3DImporter();
~AC3DImporter() override = default;
// Represents an AC3D material
struct Material {
Material() :
rgb(0.6f, 0.6f, 0.6f),
spec(1.f, 1.f, 1.f),
shin(0.f),
trans(0.f) {}
// base color of the material
aiColor3D rgb;
// ambient color of the material
aiColor3D amb;
// emissive color of the material
aiColor3D emis;
// specular color of the material
aiColor3D spec;
// shininess exponent
float shin;
// transparency. 0 == opaque
float trans;
// name of the material. optional.
std::string name;
};
// Represents an AC3D surface
struct Surface {
Surface() :
mat(0),
flags(0) {}
unsigned int mat, flags;
using SurfaceEntry = std::pair<unsigned int, aiVector2D>;
std::vector<SurfaceEntry> entries;
// Type is low nibble of flags
enum Type : uint8_t {
Polygon = 0x0,
ClosedLine = 0x1,
OpenLine = 0x2,
TriangleStrip = 0x4, // ACC extension (TORCS and Speed Dreams)
Mask = 0xf,
};
inline uint8_t GetType() const { return (flags & Mask); }
};
// Represents an AC3D object
struct Object {
Object() :
type(World),
name(),
children(),
texRepeat(1.f, 1.f),
texOffset(0.0f, 0.0f),
rotation(),
translation(),
vertices(),
surfaces(),
numRefs(0),
subDiv(0),
crease() {}
// Type description
enum Type {
World = 0x0,
Poly = 0x1,
Group = 0x2,
Light = 0x4
} type;
// name of the object
std::string name;
// object children
std::vector<Object> children;
// texture to be assigned to all surfaces of the object
// the .acc format supports up to 4 textures
std::vector<std::string> textures;
// texture repat factors (scaling for all coordinates)
aiVector2D texRepeat, texOffset;
// rotation matrix
aiMatrix3x3 rotation;
// translation vector
aiVector3D translation;
// vertices
std::vector<aiVector3D> vertices;
// surfaces
std::vector<Surface> surfaces;
// number of indices (= num verts in verbose format)
unsigned int numRefs;
// number of subdivisions to be performed on the
// imported data
unsigned int subDiv;
// max angle limit for smoothing
float crease;
};
public:
// -------------------------------------------------------------------
/** Returns whether the class can handle the format of the given file.
* See BaseImporter::CanRead() for details.
*/
bool CanRead(const std::string &pFile, IOSystem *pIOHandler,
bool checkSig) const override;
protected:
// -------------------------------------------------------------------
/** Return importer meta information.
* See #BaseImporter::GetInfo for the details */
const aiImporterDesc *GetInfo() const override;
// -------------------------------------------------------------------
/** Imports the given file into the given scene structure.
* See BaseImporter::InternReadFile() for details*/
void InternReadFile(const std::string &pFile, aiScene *pScene,
IOSystem *pIOHandler) override;
// -------------------------------------------------------------------
/** Called prior to ReadFile().
* The function is a request to the importer to update its configuration
* basing on the Importer's configuration property list.*/
void SetupProperties(const Importer *pImp) override;
private:
// -------------------------------------------------------------------
/** Get the next line from the file.
* @return false if the end of the file was reached*/
bool GetNextLine();
// -------------------------------------------------------------------
/** Load the object section. This method is called recursively to
* load subobjects, the method returns after a 'kids 0' was
* encountered.
* @objects List of output objects*/
bool LoadObjectSection(std::vector<Object> &objects);
// -------------------------------------------------------------------
/** Convert all objects into meshes and nodes.
* @param object Current object to work on
* @param meshes Pointer to the list of output meshes
* @param outMaterials List of output materials
* @param materials Material list
* @param Scenegraph node for the object */
aiNode *ConvertObjectSection(Object &object,
std::vector<aiMesh *> &meshes,
std::vector<aiMaterial *> &outMaterials,
const std::vector<Material> &materials,
aiNode *parent = nullptr);
// -------------------------------------------------------------------
/** Convert a material
* @param object Current object
* @param matSrc Source material description
* @param matDest Destination material to be filled */
void ConvertMaterial(const Object &object,
const Material &matSrc,
aiMaterial &matDest);
private:
// points to the next data line
aiBuffer mBuffer;
// Configuration option: if enabled, up to two meshes
// are generated per material: those faces who have
// their bf cull flags set are separated.
bool configSplitBFCull;
// Configuration switch: subdivision surfaces are only
// evaluated if the value is true.
bool configEvalSubdivision;
// counts how many objects we have in the tree.
// basing on this information we can find a
// good estimate how many meshes we'll have in the final scene.
unsigned int mNumMeshes;
// current list of light sources
std::vector<aiLight *> *mLights;
// name counters
unsigned int mLightsCounter, mGroupsCounter, mPolysCounter, mWorldsCounter;
};
} // end of namespace Assimp
#endif // AI_AC3DIMPORTER_H_INC

View File

@@ -0,0 +1,500 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
// Header files, Assimp.
#include "AMFImporter.hpp"
#include <assimp/DefaultIOSystem.h>
#include <assimp/fast_atof.h>
#include <assimp/StringUtils.h>
// Header files, stdlib.
#include <memory>
namespace Assimp {
static constexpr aiImporterDesc Description = {
"Additive manufacturing file format(AMF) Importer",
"smalcom",
"",
"See documentation in source code. Chapter: Limitations.",
aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
0,
0,
0,
0,
"amf"
};
void AMFImporter::Clear() {
mNodeElement_Cur = nullptr;
mUnit.clear();
mMaterial_Converted.clear();
mTexture_Converted.clear();
// Delete all elements
if (!mNodeElement_List.empty()) {
for (AMFNodeElementBase *ne : mNodeElement_List) {
delete ne;
}
mNodeElement_List.clear();
}
}
AMFImporter::AMFImporter() AI_NO_EXCEPT :
mNodeElement_Cur(nullptr),
mXmlParser(nullptr) {
// empty
}
AMFImporter::~AMFImporter() {
delete mXmlParser;
// Clear() is accounting if data already is deleted. So, just check again if all data is deleted.
Clear();
}
/*********************************************************************************************************************************************/
/************************************************************ Functions: find set ************************************************************/
/*********************************************************************************************************************************************/
bool AMFImporter::Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const {
for (AMFNodeElementBase *ne : mNodeElement_List) {
if ((ne->ID == pID) && (ne->Type == pType)) {
if (pNodeElement != nullptr) {
*pNodeElement = ne;
}
return true;
}
} // for(CAMFImporter_NodeElement* ne: mNodeElement_List)
return false;
}
bool AMFImporter::Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const {
aiString node_name(pID.c_str());
for (aiNode *node : nodeArray) {
if (node->mName == node_name) {
if (pNode != nullptr) {
*pNode = node;
}
return true;
}
} // for(aiNode* node: pNodeList)
return false;
}
bool AMFImporter::Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const {
for (const SPP_Material &mat : mMaterial_Converted) {
if (mat.ID == pID) {
if (pConvertedMaterial != nullptr) {
*pConvertedMaterial = &mat;
}
return true;
}
} // for(const SPP_Material& mat: mMaterial_Converted)
return false;
}
/*********************************************************************************************************************************************/
/************************************************************ Functions: throw set ***********************************************************/
/*********************************************************************************************************************************************/
void AMFImporter::Throw_CloseNotFound(const std::string &nodeName) {
throw DeadlyImportError("Close tag for node <" + nodeName + "> not found. Seems file is corrupt.");
}
void AMFImporter::Throw_IncorrectAttr(const std::string &nodeName, const std::string &attrName) {
throw DeadlyImportError("Node <" + nodeName + "> has incorrect attribute \"" + attrName + "\".");
}
void AMFImporter::Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &attrName) {
throw DeadlyImportError("Attribute \"" + attrName + "\" in node <" + nodeName + "> has incorrect value.");
}
void AMFImporter::Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) {
throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + nodeName + ". Description: " + pDescription);
}
void AMFImporter::Throw_ID_NotFound(const std::string &pID) const {
throw DeadlyImportError("Not found node with name \"", pID, "\".");
}
/*********************************************************************************************************************************************/
/************************************************************* Functions: XML set ************************************************************/
/*********************************************************************************************************************************************/
void AMFImporter::XML_CheckNode_MustHaveChildren(pugi::xml_node &node) {
if (node.children().begin() == node.children().end()) {
throw DeadlyImportError(std::string("Node <") + node.name() + "> must have children.");
}
}
bool AMFImporter::XML_SearchNode(const std::string &nodeName) {
return nullptr != mXmlParser->findNode(nodeName);
}
static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) {
return (isalnum((unsigned char)pChar) || (pChar == '+') || (pChar == '/'));
}
void AMFImporter::ParseHelper_Decode_Base64(const std::string &pInputBase64, std::vector<uint8_t> &pOutputData) const {
// With help from
// René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html
const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
uint8_t tidx = 0;
uint8_t arr4[4], arr3[3];
// check input data
if (pInputBase64.size() % 4) {
throw DeadlyImportError("Base64-encoded data must have size multiply of four.");
}
// prepare output place
pOutputData.clear();
pOutputData.reserve(pInputBase64.size() / 4 * 3);
for (size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--) {
if (ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) {
arr4[tidx++] = pInputBase64[in_idx++];
if (tidx == 4) {
for (tidx = 0; tidx < 4; tidx++)
arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]);
arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
for (tidx = 0; tidx < 3; tidx++)
pOutputData.push_back(arr3[tidx]);
tidx = 0;
} // if(tidx == 4)
} // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
else {
in_idx++;
} // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else
}
if (tidx) {
for (uint8_t i = tidx; i < 4; i++)
arr4[i] = 0;
for (uint8_t i = 0; i < 4; i++)
arr4[i] = (uint8_t)(base64_chars.find(arr4[i]));
arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
for (uint8_t i = 0; i < (tidx - 1); i++)
pOutputData.push_back(arr3[i]);
}
}
void AMFImporter::ParseFile(const std::string &pFile, IOSystem *pIOHandler) {
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
// Check whether we can read from the file
if (file == nullptr) {
throw DeadlyImportError("Failed to open AMF file ", pFile, ".");
}
mXmlParser = new XmlParser();
if (!mXmlParser->parse(file.get())) {
delete mXmlParser;
mXmlParser = nullptr;
throw DeadlyImportError("Failed to create XML reader for file ", pFile, ".");
}
// Start reading, search for root tag <amf>
if (!mXmlParser->hasNode("amf")) {
throw DeadlyImportError("Root node \"amf\" not found.");
}
ParseNode_Root();
} // namespace Assimp
void AMFImporter::ParseHelper_Node_Enter(AMFNodeElementBase *node) {
mNodeElement_Cur->Child.push_back(node); // add new element to current element child list.
mNodeElement_Cur = node;
}
void AMFImporter::ParseHelper_Node_Exit() {
if (mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent;
}
// <amf
// unit="" - The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
// version="" - Version of file format.
// >
// </amf>
// Root XML element.
// Multi elements - No.
void AMFImporter::ParseNode_Root() {
AMFNodeElementBase *ne = nullptr;
XmlNode *root = mXmlParser->findNode("amf");
if (nullptr == root) {
throw DeadlyImportError("Root node \"amf\" not found.");
}
XmlNode node = *root;
mUnit = ai_tolower(std::string(node.attribute("unit").as_string()));
mVersion = node.attribute("version").as_string();
// Read attributes for node <amf>.
// Check attributes
if (!mUnit.empty()) {
if ((mUnit != "inch") && (mUnit != "millimeters") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) {
Throw_IncorrectAttrValue("unit", mUnit);
}
}
// create root node element.
ne = new AMFRoot(nullptr);
mNodeElement_Cur = ne; // set first "current" element
// and assign attribute's values
((AMFRoot *)ne)->Unit = mUnit;
((AMFRoot *)ne)->Version = mVersion;
// Check for child nodes
for (XmlNode &currentNode : node.children() ) {
const std::string currentName = currentNode.name();
if (currentName == "object") {
ParseNode_Object(currentNode);
} else if (currentName == "material") {
ParseNode_Material(currentNode);
} else if (currentName == "texture") {
ParseNode_Texture(currentNode);
} else if (currentName == "constellation") {
ParseNode_Constellation(currentNode);
} else if (currentName == "metadata") {
ParseNode_Metadata(currentNode);
}
mNodeElement_Cur = ne;
}
mNodeElement_Cur = ne; // force restore "current" element
mNodeElement_List.push_back(ne); // add to node element list because its a new object in graph.
}
// <constellation
// id="" - The Object ID of the new constellation being defined.
// >
// </constellation>
// A collection of objects or constellations with specific relative locations.
// Multi elements - Yes.
// Parent element - <amf>.
void AMFImporter::ParseNode_Constellation(XmlNode &node) {
std::string id = node.attribute("id").as_string();
// create and if needed - define new grouping object.
AMFNodeElementBase *ne = new AMFConstellation(mNodeElement_Cur);
AMFConstellation &als = *((AMFConstellation *)ne); // alias for convenience
if (!id.empty()) {
als.ID = id;
}
// Check for child nodes
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
std::string name = currentNode.name();
if (name == "instance") {
ParseNode_Instance(currentNode);
} else if (name == "metadata") {
ParseNode_Metadata(currentNode);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne);
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <instance
// objectid="" - The Object ID of the new constellation being defined.
// >
// </instance>
// A collection of objects or constellations with specific relative locations.
// Multi elements - Yes.
// Parent element - <amf>.
void AMFImporter::ParseNode_Instance(XmlNode &node) {
AMFNodeElementBase *ne(nullptr);
// Read attributes for node <constellation>.
std::string objectid = node.attribute("objectid").as_string();
// used object id must be defined, check that.
if (objectid.empty()) {
throw DeadlyImportError("\"objectid\" in <instance> must be defined.");
}
// create and define new grouping object.
ne = new AMFInstance(mNodeElement_Cur);
AMFInstance &als = *((AMFInstance *)ne);
als.ObjectID = objectid;
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
for (auto &currentNode : node.children()) {
const std::string &currentName = currentNode.name();
if (currentName == "deltax") {
XmlParser::getValueAsReal(currentNode, als.Delta.x);
} else if (currentName == "deltay") {
XmlParser::getValueAsReal(currentNode, als.Delta.y);
} else if (currentName == "deltaz") {
XmlParser::getValueAsReal(currentNode, als.Delta.z);
} else if (currentName == "rx") {
XmlParser::getValueAsReal(currentNode, als.Delta.x);
} else if (currentName == "ry") {
XmlParser::getValueAsReal(currentNode, als.Delta.y);
} else if (currentName == "rz") {
XmlParser::getValueAsReal(currentNode, als.Delta.z);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne);
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <object
// id="" - A unique ObjectID for the new object being defined.
// >
// </object>
// An object definition.
// Multi elements - Yes.
// Parent element - <amf>.
void AMFImporter::ParseNode_Object(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
// Read attributes for node <object>.
std::string id = node.attribute("id").as_string();
// create and if needed - define new geometry object.
ne = new AMFObject(mNodeElement_Cur);
AMFObject &als = *((AMFObject *)ne); // alias for convenience
if (!id.empty()) {
als.ID = id;
}
// Check for child nodes
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
for (auto &currentNode : node.children()) {
const std::string &currentName = currentNode.name();
if (currentName == "color") {
ParseNode_Color(currentNode);
} else if (currentName == "mesh") {
ParseNode_Mesh(currentNode);
} else if (currentName == "metadata") {
ParseNode_Metadata(currentNode);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <metadata
// type="" - The type of the attribute.
// >
// </metadata>
// Specify additional information about an entity.
// Multi elements - Yes.
// Parent element - <amf>, <object>, <volume>, <material>, <vertex>.
//
// Reserved types are:
// "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user.
// "Description" - A description of the content of the entity
// "URL" - A link to an external resource relating to the entity
// "Author" - Specifies the name(s) of the author(s) of the entity
// "Company" - Specifying the company generating the entity
// "CAD" - specifies the name of the originating CAD software and version
// "Revision" - specifies the revision of the entity
// "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system
// "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only)
void AMFImporter::ParseNode_Metadata(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
std::string type = node.attribute("type").as_string(), value;
XmlParser::getValueAsString(node, value);
// read attribute
ne = new AMFMetadata(mNodeElement_Cur);
((AMFMetadata *)ne)->MetaType = type;
((AMFMetadata *)ne)->Value = value;
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
bool AMFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*pCheckSig*/) const {
static const char *tokens[] = { "<amf" };
return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
}
const aiImporterDesc *AMFImporter::GetInfo() const {
return &Description;
}
void AMFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
Clear(); // delete old graph.
ParseFile(pFile, pIOHandler);
Postprocess_BuildScene(pScene);
// scene graph is ready, exit.
}
} // namespace Assimp
#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

View File

@@ -0,0 +1,308 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/// \file AMFImporter.hpp
/// \brief AMF-format files importer for Assimp.
/// \date 2016
/// \author smal.root@gmail.com
// Thanks to acorn89 for support.
#pragma once
#ifndef INCLUDED_AI_AMF_IMPORTER_H
#define INCLUDED_AI_AMF_IMPORTER_H
#include "AMFImporter_Node.hpp"
// Header files, Assimp.
#include "assimp/types.h"
#include <assimp/BaseImporter.h>
#include <assimp/XmlParser.h>
#include <assimp/importerdesc.h>
#include <assimp/DefaultLogger.hpp>
// Header files, stdlib.
#include <set>
namespace Assimp {
/// \class AMFImporter
/// Class that holding scene graph which include: geometry, metadata, materials etc.
///
/// Implementing features.
///
/// Limitations.
///
/// 1. When for texture mapping used set of source textures (r, g, b, a) not only one then attribute "tiled" for all set will be true if it true in any of
/// source textures.
/// Example. Triangle use for texture mapping three textures. Two of them has "tiled" set to false and one - set to true. In scene all three textures
/// will be tiled.
///
/// Unsupported features:
/// 1. Node <composite>, formulas in <composite> and <color>. For implementing this feature can be used expression parser "muParser" like in project
/// "amf_tools".
/// 2. Attribute "profile" in node <color>.
/// 3. Curved geometry: <edge>, <normal> and children nodes of them.
/// 4. Attributes: "unit" and "version" in <amf> read but do nothing.
/// 5. <metadata> stored only for root node <amf>.
/// 6. Color averaging of vertices for which <triangle>'s set different colors.
///
/// Supported nodes:
/// General:
/// <amf>; <constellation>; <instance> and children <deltax>, <deltay>, <deltaz>, <rx>, <ry>, <rz>; <metadata>;
///
/// Geometry:
/// <object>; <mesh>; <vertices>; <vertex>; <coordinates> and children <x>, <y>, <z>; <volume>; <triangle> and children <v1>, <v2>, <v3>;
///
/// Material:
/// <color> and children <r>, <g>, <b>, <a>; <texture>; <material>;
/// two variants of texture coordinates:
/// new - <texmap> and children <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3>
/// old - <map> and children <u1>, <u2>, <u3>, <v1>, <v2>, <v3>
///
class AMFImporter final : public BaseImporter {
using AMFMetaDataArray = std::vector<AMFMetadata *>;
using MeshArray = std::vector<aiMesh *>;
using NodeArray = std::vector<aiNode *>;
public:
struct SPP_Material;
/// Data type for post-processing step. More suitable container for part of material's composition.
struct SPP_Composite {
SPP_Material *Material; ///< Pointer to material - part of composition.
std::string Formula; ///< Formula for calculating ratio of \ref Material.
};
/// Data type for post-processing step. More suitable container for texture.
struct SPP_Texture {
std::string ID;
size_t Width, Height, Depth;
bool Tiled;
char FormatHint[9]; // 8 for string + 1 for terminator.
uint8_t *Data;
};
/// Data type for post-processing step. Contain face data.
struct SComplexFace {
aiFace Face; ///< Face vertices.
const AMFColor *Color; ///< Face color. Equal to nullptr if color is not set for the face.
const AMFTexMap *TexMap; ///< Face texture mapping data. Equal to nullptr if texture mapping is not set for the face.
};
/// Data type for post-processing step. More suitable container for material.
struct SPP_Material {
std::string ID; ///< Material ID.
std::list<AMFMetadata *> Metadata; ///< Metadata of material.
AMFColor *Color; ///< Color of material.
std::list<SPP_Composite> Composition; ///< List of child materials if current material is composition of few another.
/// Return color calculated for specified coordinate.
/// \param [in] pX - "x" coordinate.
/// \param [in] pY - "y" coordinate.
/// \param [in] pZ - "z" coordinate.
/// \return calculated color.
aiColor4D GetColor(const float pX, const float pY, const float pZ) const;
};
/// Default constructor.
AMFImporter() AI_NO_EXCEPT;
/// Default destructor.
~AMFImporter() override;
/// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph.
/// Also exception can be thrown if trouble will found.
/// \param [in] pFile - name of file to be parsed.
/// \param [in] pIOHandler - pointer to IO helper object.
void ParseFile(const std::string &pFile, IOSystem *pIOHandler);
void ParseHelper_Node_Enter(AMFNodeElementBase *child);
void ParseHelper_Node_Exit();
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const override;
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
const aiImporterDesc *GetInfo() const override;
bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const;
bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const;
bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const;
AI_WONT_RETURN void Throw_CloseNotFound(const std::string &nodeName) AI_WONT_RETURN_SUFFIX;
AI_WONT_RETURN void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
AI_WONT_RETURN void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
AI_WONT_RETURN void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) AI_WONT_RETURN_SUFFIX;
AI_WONT_RETURN void Throw_ID_NotFound(const std::string &pID) const AI_WONT_RETURN_SUFFIX;
void XML_CheckNode_MustHaveChildren(pugi::xml_node &node);
bool XML_SearchNode(const std::string &nodeName);
AMFImporter(const AMFImporter &pScene) = delete;
AMFImporter &operator=(const AMFImporter &pScene) = delete;
private:
/// Clear all temporary data.
void Clear();
/// Get data stored in <vertices> and place it to arrays.
/// \param [in] pNodeElement - reference to node element which kept <object> data.
/// \param [in] pVertexCoordinateArray - reference to vertices coordinates kept in <vertices>.
/// \param [in] pVertexColorArray - reference to vertices colors for all <vertex's. If color for vertex is not set then corresponding member of array
/// contain nullptr.
void PostprocessHelper_CreateMeshDataArray(const AMFMesh &pNodeElement, std::vector<aiVector3D> &pVertexCoordinateArray,
std::vector<AMFColor *> &pVertexColorArray) const;
/// Return converted texture ID which related to specified source textures ID's. If converted texture does not exist then it will be created and ID on new
/// converted texture will be returned. Conversion: set of textures from \ref CAMFImporter_NodeElement_Texture to one \ref SPP_Texture and place it
/// to converted textures list.
/// Any of source ID's can be absent(empty string) or even one ID only specified. But at least one ID must be specified.
/// \param [in] pID_R - ID of source "red" texture.
/// \param [in] pID_G - ID of source "green" texture.
/// \param [in] pID_B - ID of source "blue" texture.
/// \param [in] pID_A - ID of source "alpha" texture.
/// \return index of the texture in array of the converted textures.
size_t PostprocessHelper_GetTextureID_Or_Create(const std::string &pID_R, const std::string &pID_G, const std::string &pID_B, const std::string &pID_A);
/// Separate input list by texture IDs. This step is needed because aiMesh can contain mesh which is use only one texture (or set: diffuse, bump etc).
/// \param [in] pInputList - input list with faces. Some of them can contain color or texture mapping, or both of them, or nothing. Will be cleared after
/// processing.
/// \param [out] pOutputList_Separated - output list of the faces lists. Separated faces list by used texture IDs. Will be cleared before processing.
void PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> &pInputList, std::list<std::list<SComplexFace>> &pOutputList_Separated);
/// Check if child elements of node element is metadata and add it to scene node.
/// \param [in] pMetadataList - reference to list with collected metadata.
/// \param [out] pSceneNode - scene node in which metadata will be added.
void Postprocess_AddMetadata(const AMFMetaDataArray &pMetadataList, aiNode &pSceneNode) const;
/// To create aiMesh and aiNode for it from <object>.
/// \param [in] pNodeElement - reference to node element which kept <object> data.
/// \param [out] meshList - reference to a list with all aiMesh of the scene.
/// \param [out] pSceneNode - pointer to place where new aiNode will be created.
void Postprocess_BuildNodeAndObject(const AMFObject &pNodeElement, MeshArray &meshList, aiNode **pSceneNode);
/// Create mesh for every <volume> in <mesh>.
/// \param [in] pNodeElement - reference to node element which kept <mesh> data.
/// \param [in] pVertexCoordinateArray - reference to vertices coordinates for all <volume>'s.
/// \param [in] pVertexColorArray - reference to vertices colors for all <volume>'s. If color for vertex is not set then corresponding member of array
/// contain nullptr.
/// \param [in] pObjectColor - pointer to colors for <object>. If color is not set then argument contain nullptr.
/// \param [in] pMaterialList - reference to a list with defined materials.
/// \param [out] pMeshList - reference to a list with all aiMesh of the scene.
/// \param [out] pSceneNode - reference to aiNode which will own new aiMesh's.
void Postprocess_BuildMeshSet(const AMFMesh &pNodeElement, const std::vector<aiVector3D> &pVertexCoordinateArray,
const std::vector<AMFColor *> &pVertexColorArray, const AMFColor *pObjectColor,
MeshArray &pMeshList, aiNode &pSceneNode);
/// Convert material from \ref CAMFImporter_NodeElement_Material to \ref SPP_Material.
/// \param [in] pMaterial - source CAMFImporter_NodeElement_Material.
void Postprocess_BuildMaterial(const AMFMaterial &pMaterial);
/// Create and add to aiNode's list new part of scene graph defined by <constellation>.
/// \param [in] pConstellation - reference to <constellation> node.
/// \param [out] nodeArray - reference to aiNode's list.
void Postprocess_BuildConstellation(AMFConstellation &pConstellation, NodeArray &nodeArray) const;
/// Build Assimp scene graph in aiScene from collected data.
/// \param [out] pScene - pointer to aiScene where tree will be built.
void Postprocess_BuildScene(aiScene *pScene);
/// Decode Base64-encoded data.
/// \param [in] pInputBase64 - reference to input Base64-encoded string.
/// \param [out] pOutputData - reference to output array for decoded data.
void ParseHelper_Decode_Base64(const std::string &pInputBase64, std::vector<uint8_t> &pOutputData) const;
/// Parse <AMF> node of the file.
void ParseNode_Root();
/// Parse <constellation> node of the file.
void ParseNode_Constellation(XmlNode &node);
/// Parse <instance> node of the file.
void ParseNode_Instance(XmlNode &node);
/// Parse <material> node of the file.
void ParseNode_Material(XmlNode &node);
/// Parse <metadata> node.
void ParseNode_Metadata(XmlNode &node);
/// Parse <object> node of the file.
void ParseNode_Object(XmlNode &node);
/// Parse <texture> node of the file.
void ParseNode_Texture(XmlNode &node);
/// Parse <coordinates> node of the file.
void ParseNode_Coordinates(XmlNode &node);
/// Parse <edge> node of the file.
void ParseNode_Edge(XmlNode &node);
/// Parse <mesh> node of the file.
void ParseNode_Mesh(XmlNode &node);
/// Parse <triangle> node of the file.
void ParseNode_Triangle(XmlNode &node);
/// Parse <vertex> node of the file.
void ParseNode_Vertex(XmlNode &node);
/// Parse <vertices> node of the file.
void ParseNode_Vertices(XmlNode &node);
/// Parse <volume> node of the file.
void ParseNode_Volume(XmlNode &node);
/// Parse <color> node of the file.
void ParseNode_Color(XmlNode &node);
/// Parse <texmap> of <map> node of the file.
/// \param [in] pUseOldName - if true then use old name of node(and children) - <map>, instead of new name - <texmap>.
void ParseNode_TexMap(XmlNode &node, const bool pUseOldName = false);
private:
AMFNodeElementBase *mNodeElement_Cur; ///< Current element.
std::list<AMFNodeElementBase *> mNodeElement_List; ///< All elements of scene graph.
XmlParser *mXmlParser;
std::string mUnit;
std::string mVersion;
std::list<SPP_Material> mMaterial_Converted; ///< List of converted materials for postprocessing step.
std::list<SPP_Texture> mTexture_Converted; ///< List of converted textures for postprocessing step.
};
} // namespace Assimp
#endif // INCLUDED_AI_AMF_IMPORTER_H

View File

@@ -0,0 +1,284 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
#include "AMFImporter.hpp"
#include <assimp/ParsingUtils.h>
namespace Assimp {
// <mesh>
// </mesh>
// A 3D mesh hull.
// Multi elements - Yes.
// Parent element - <object>.
void AMFImporter::ParseNode_Mesh(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
// Check for child nodes
if (0 != ASSIMP_stricmp(node.name(), "mesh")) {
return;
}
// create new mesh object.
ne = new AMFMesh(mNodeElement_Cur);
bool found_verts = false, found_volumes = false;
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
pugi::xml_node vertNode = node.child("vertices");
if (!vertNode.empty()) {
ParseNode_Vertices(vertNode);
found_verts = true;
}
pugi::xml_node volumeNode = node.child("volume");
if (!volumeNode.empty()) {
ParseNode_Volume(volumeNode);
found_volumes = true;
}
ParseHelper_Node_Exit();
}
if (!found_verts && !found_volumes) {
mNodeElement_Cur->Child.push_back(ne);
} // if(!mReader->isEmptyElement()) else
// and to node element list because its a new object in graph.
mNodeElement_List.push_back(ne);
}
// <vertices>
// </vertices>
// The list of vertices to be used in defining triangles.
// Multi elements - No.
// Parent element - <mesh>.
void AMFImporter::ParseNode_Vertices(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
// create new mesh object.
ne = new AMFVertices(mNodeElement_Cur);
// Check for child nodes
if (node.empty()) {
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
return;
}
ParseHelper_Node_Enter(ne);
for (XmlNode &currentNode : node.children()) {
const std::string &currentName = currentNode.name();
if (currentName == "vertex") {
ParseNode_Vertex(currentNode);
}
}
ParseHelper_Node_Exit();
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <vertex>
// </vertex>
// A vertex to be referenced in triangles.
// Multi elements - Yes.
// Parent element - <vertices>.
void AMFImporter::ParseNode_Vertex(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
// create new mesh object.
ne = new AMFVertex(mNodeElement_Cur);
// Check for child nodes
pugi::xml_node colorNode = node.child("color");
bool col_read = false;
bool coord_read = false;
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
if (!colorNode.empty()) {
ParseNode_Color(colorNode);
col_read = true;
}
pugi::xml_node coordNode = node.child("coordinates");
if (!coordNode.empty()) {
ParseNode_Coordinates(coordNode);
coord_read = true;
}
ParseHelper_Node_Exit();
}
if (!coord_read && !col_read) {
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <coordinates>
// </coordinates>
// Specifies the 3D location of this vertex.
// Multi elements - No.
// Parent element - <vertex>.
//
// Children elements:
// <x>, <y>, <z>
// Multi elements - No.
// X, Y, or Z coordinate, respectively, of a vertex position in space.
void AMFImporter::ParseNode_Coordinates(XmlNode &node) {
AMFNodeElementBase *ne = nullptr;
if (!node.empty()) {
ne = new AMFCoordinates(mNodeElement_Cur);
ParseHelper_Node_Enter(ne);
for (XmlNode &currentNode : node.children()) {
// create new color object.
AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience
const std::string &currentName = ai_tolower(currentNode.name());
if (currentName == "x") {
XmlParser::getValueAsReal(currentNode, als.Coordinate.x);
} else if (currentName == "y") {
XmlParser::getValueAsReal(currentNode, als.Coordinate.y);
} else if (currentName == "z") {
XmlParser::getValueAsReal(currentNode, als.Coordinate.z);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(new AMFCoordinates(mNodeElement_Cur));
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <volume
// materialid="" - Which material to use.
// type="" - What this volume describes can be "region" or "support". If none specified, "object" is assumed. If support, then the geometric
// requirements 1-8 listed in section 5 do not need to be maintained.
// >
// </volume>
// Defines a volume from the established vertex list.
// Multi elements - Yes.
// Parent element - <mesh>.
void AMFImporter::ParseNode_Volume(XmlNode &node) {
std::string materialid;
std::string type;
AMFNodeElementBase *ne = new AMFVolume(mNodeElement_Cur);
// Read attributes for node <color>.
// and assign read data
((AMFVolume *)ne)->MaterialID = node.attribute("materialid").as_string();
((AMFVolume *)ne)->VolumeType = type;
// Check for child nodes
bool col_read = false;
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
for (auto &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == "color") {
if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for <volume>.");
ParseNode_Color(currentNode);
col_read = true;
} else if (currentName == "triangle") {
ParseNode_Triangle(currentNode);
} else if (currentName == "metadata") {
ParseNode_Metadata(currentNode);
} else if (currentName == "volume") {
ParseNode_Metadata(currentNode);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
// <triangle>
// </triangle>
// Defines a 3D triangle from three vertices, according to the right-hand rule (counter-clockwise when looking from the outside).
// Multi elements - Yes.
// Parent element - <volume>.
//
// Children elements:
// <v1>, <v2>, <v3>
// Multi elements - No.
// Index of the desired vertices in a triangle or edge.
void AMFImporter::ParseNode_Triangle(XmlNode &node) {
AMFNodeElementBase *ne = new AMFTriangle(mNodeElement_Cur);
// create new triangle object.
AMFTriangle &als = *((AMFTriangle *)ne); // alias for convenience
bool col_read = false;
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
std::string v;
for (auto &currentNode : node.children()) {
const std::string currentName = currentNode.name();
if (currentName == "color") {
if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for <triangle>.");
ParseNode_Color(currentNode);
col_read = true;
} else if (currentName == "texmap") {
ParseNode_TexMap(currentNode);
} else if (currentName == "map") {
ParseNode_TexMap(currentNode, true);
} else if (currentName == "v1") {
XmlParser::getValueAsString(currentNode, v);
als.V[0] = std::atoi(v.c_str());
} else if (currentName == "v2") {
XmlParser::getValueAsString(currentNode, v);
als.V[1] = std::atoi(v.c_str());
} else if (currentName == "v3") {
XmlParser::getValueAsString(currentNode, v);
als.V[2] = std::atoi(v.c_str());
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
}
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
}
} // namespace Assimp
#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

View File

@@ -0,0 +1,326 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/// \file AMFImporter_Material.cpp
/// \brief Parsing data from material nodes.
/// \date 2016
/// \author smal.root@gmail.com
#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
#include "AMFImporter.hpp"
namespace Assimp {
// <color
// profile="" - The ICC color space used to interpret the three color channels <r>, <g> and <b>.
// >
// </color>
// A color definition.
// Multi elements - No.
// Parent element - <material>, <object>, <volume>, <vertex>, <triangle>.
//
// "profile" can be one of "sRGB", "AdobeRGB", "Wide-Gamut-RGB", "CIERGB", "CIELAB", or "CIEXYZ".
// Children elements:
// <r>, <g>, <b>, <a>
// Multi elements - No.
// Red, Greed, Blue and Alpha (transparency) component of a color in sRGB space, values ranging from 0 to 1. The
// values can be specified as constants, or as a formula depending on the coordinates.
void AMFImporter::ParseNode_Color(XmlNode &node) {
if (node.empty()) {
return;
}
const std::string &profile = node.attribute("profile").as_string();
bool read_flag[4] = { false, false, false, false };
AMFNodeElementBase *ne = new AMFColor(mNodeElement_Cur);
AMFColor &als = *((AMFColor *)ne); // alias for convenience
ParseHelper_Node_Enter(ne);
for (pugi::xml_node &child : node.children()) {
// create new color object.
als.Profile = profile;
const std::string &name = child.name();
if ( name == "r") {
read_flag[0] = true;
XmlParser::getValueAsFloat(child, als.Color.r);
} else if (name == "g") {
read_flag[1] = true;
XmlParser::getValueAsFloat(child, als.Color.g);
} else if (name == "b") {
read_flag[2] = true;
XmlParser::getValueAsFloat(child, als.Color.b);
} else if (name == "a") {
read_flag[3] = true;
XmlParser::getValueAsFloat(child, als.Color.a);
}
// check if <a> is absent. Then manually add "a == 1".
if (!read_flag[3]) {
als.Color.a = 1;
}
}
als.Composed = false;
mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
ParseHelper_Node_Exit();
// check that all components was defined
if (!(read_flag[0] && read_flag[1] && read_flag[2])) {
throw DeadlyImportError("Not all color components are defined.");
}
}
// <material
// id="" - A unique material id. material ID "0" is reserved to denote no material (void) or sacrificial material.
// >
// </material>
// An available material.
// Multi elements - Yes.
// Parent element - <amf>.
void AMFImporter::ParseNode_Material(XmlNode &node) {
// create new object and assign read data
std::string id = node.attribute("id").as_string();
AMFNodeElementBase *ne = new AMFMaterial(mNodeElement_Cur);
((AMFMaterial*)ne)->ID = id;
// Check for child nodes
if (!node.empty()) {
ParseHelper_Node_Enter(ne);
for (pugi::xml_node &child : node.children()) {
const std::string name = child.name();
if (name == "color") {
ParseNode_Color(child);
} else if (name == "metadata") {
ParseNode_Metadata(child);
}
}
ParseHelper_Node_Exit();
} else {
mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
}
mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
}
// <texture
// id="" - Assigns a unique texture id for the new texture.
// width="" - Width (horizontal size, x) of the texture, in pixels.
// height="" - Height (lateral size, y) of the texture, in pixels.
// depth="" - Depth (vertical size, z) of the texture, in pixels.
// type="" - Encoding of the data in the texture. Currently allowed values are "grayscale" only. In grayscale mode, each pixel is represented by one byte
// in the range of 0-255. When the texture is referenced using the tex function, these values are converted into a single floating point number in the
// range of 0-1 (see Annex 2). A full color graphics will typically require three textures, one for each of the color channels. A graphic involving
// transparency may require a fourth channel.
// tiled="" - If true then texture repeated when UV-coordinates is greater than 1.
// >
// </triangle>
// Specifies an texture data to be used as a map. Lists a sequence of Base64 values specifying values for pixels from left to right then top to bottom,
// then layer by layer.
// Multi elements - Yes.
// Parent element - <amf>.
void AMFImporter::ParseNode_Texture(XmlNode &node) {
const std::string id = node.attribute("id").as_string();
const uint32_t width = node.attribute("width").as_uint();
const uint32_t height = node.attribute("height").as_uint();
uint32_t depth = node.attribute("depth").as_uint();
const std::string type = node.attribute("type").as_string();
bool tiled = node.attribute("tiled").as_bool();
if (node.empty()) {
return;
}
// create new texture object.
AMFNodeElementBase *ne = new AMFTexture(mNodeElement_Cur);
AMFTexture& als = *((AMFTexture*)ne);// alias for convenience
std::string enc64_data;
XmlParser::getValueAsString(node, enc64_data);
// Check for child nodes
// check that all components was defined
if (id.empty()) {
throw DeadlyImportError("ID for texture must be defined.");
}
if (width < 1) {
throw DeadlyImportError("Invalid width for texture.");
}
if (height < 1) {
throw DeadlyImportError("Invalid height for texture.");
}
if (type != "grayscale") {
throw DeadlyImportError("Invalid type for texture.");
}
if (enc64_data.empty()) {
throw DeadlyImportError("Texture data not defined.");
}
// copy data
als.ID = id;
als.Width = width;
als.Height = height;
als.Depth = depth;
als.Tiled = tiled;
ParseHelper_Decode_Base64(enc64_data, als.Data);
if (depth == 0) {
depth = (uint32_t)(als.Data.size() / (width * height));
}
// check data size
if ((width * height * depth) != als.Data.size()) {
throw DeadlyImportError("Texture has incorrect data size.");
}
mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
}
// <texmap
// rtexid="" - Texture ID for red color component.
// gtexid="" - Texture ID for green color component.
// btexid="" - Texture ID for blue color component.
// atexid="" - Texture ID for alpha color component. Optional.
// >
// </texmap>, old name: <map>
// Specifies texture coordinates for triangle.
// Multi elements - No.
// Parent element - <triangle>.
// Children elements:
// <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3>. Old name: <u1>, <u2>, <u3>, <v1>, <v2>, <v3>.
// Multi elements - No.
// Texture coordinates for every vertex of triangle.
void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) {
// Read attributes for node <color>.
AMFNodeElementBase *ne = new AMFTexMap(mNodeElement_Cur);
AMFTexMap &als = *((AMFTexMap *)ne); //
std::string rtexid, gtexid, btexid, atexid;
if (!node.empty()) {
for (pugi::xml_attribute &attr : node.attributes()) {
const std::string &currentAttr = attr.name();
if (currentAttr == "rtexid") {
rtexid = attr.as_string();
} else if (currentAttr == "gtexid") {
gtexid = attr.as_string();
} else if (currentAttr == "btexid") {
btexid = attr.as_string();
} else if (currentAttr == "atexid") {
atexid = attr.as_string();
}
}
}
// create new texture coordinates object, alias for convenience
// check data
if (rtexid.empty() && gtexid.empty() && btexid.empty()) {
throw DeadlyImportError("ParseNode_TexMap. At least one texture ID must be defined.");
}
// Check for children nodes
if (node.children().begin() == node.children().end()) {
throw DeadlyImportError("Invalid children definition.");
}
// read children nodes
bool read_flag[6] = { false, false, false, false, false, false };
if (!pUseOldName) {
ParseHelper_Node_Enter(ne);
for ( XmlNode &currentNode : node.children()) {
const std::string &name = currentNode.name();
if (name == "utex1") {
read_flag[0] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].x);
} else if (name == "utex2") {
read_flag[1] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].x);
} else if (name == "utex3") {
read_flag[2] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].x);
} else if (name == "vtex1") {
read_flag[3] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].y);
} else if (name == "vtex2") {
read_flag[4] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].y);
} else if (name == "vtex3") {
read_flag[5] = true;
XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].y);
}
}
ParseHelper_Node_Exit();
} else {
for (pugi::xml_attribute &attr : node.attributes()) {
const std::string name = attr.name();
if (name == "u") {
read_flag[0] = true;
als.TextureCoordinate[0].x = attr.as_float();
} else if (name == "u2") {
read_flag[1] = true;
als.TextureCoordinate[1].x = attr.as_float();
} else if (name == "u3") {
read_flag[2] = true;
als.TextureCoordinate[2].x = attr.as_float();
} else if (name == "v1") {
read_flag[3] = true;
als.TextureCoordinate[0].y = attr.as_float();
} else if (name == "v2") {
read_flag[4] = true;
als.TextureCoordinate[1].y = attr.as_float();
} else if (name == "v3") {
read_flag[5] = true;
als.TextureCoordinate[0].y = attr.as_float();
}
}
}
// check that all components was defined
if (!(read_flag[0] && read_flag[1] && read_flag[2] && read_flag[3] && read_flag[4] && read_flag[5])) {
throw DeadlyImportError("Not all texture coordinates are defined.");
}
// copy attributes data
als.TextureID_R = rtexid;
als.TextureID_G = gtexid;
als.TextureID_B = btexid;
als.TextureID_A = atexid;
mNodeElement_List.push_back(ne);
}
}// namespace Assimp
#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

View File

@@ -0,0 +1,306 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/// \file AMFImporter_Node.hpp
/// \brief Elements of scene graph.
/// \date 2016
/// \author smal.root@gmail.com
#pragma once
#ifndef INCLUDED_AI_AMF_IMPORTER_NODE_H
#define INCLUDED_AI_AMF_IMPORTER_NODE_H
// Header files, Assimp.
#include <assimp/scene.h>
#include <assimp/types.h>
#include <list>
#include <string>
#include <vector>
/// Base class for elements of nodes.
class AMFNodeElementBase {
public:
/// Define what data type contain node element.
enum EType {
ENET_Color, ///< Color element: <color>.
ENET_Constellation, ///< Grouping element: <constellation>.
ENET_Coordinates, ///< Coordinates element: <coordinates>.
ENET_Edge, ///< Edge element: <edge>.
ENET_Instance, ///< Grouping element: <constellation>.
ENET_Material, ///< Material element: <material>.
ENET_Metadata, ///< Metadata element: <metadata>.
ENET_Mesh, ///< Metadata element: <mesh>.
ENET_Object, ///< Element which hold object: <object>.
ENET_Root, ///< Root element: <amf>.
ENET_Triangle, ///< Triangle element: <triangle>.
ENET_TexMap, ///< Texture coordinates element: <texmap> or <map>.
ENET_Texture, ///< Texture element: <texture>.
ENET_Vertex, ///< Vertex element: <vertex>.
ENET_Vertices, ///< Vertex element: <vertices>.
ENET_Volume, ///< Volume element: <volume>.
ENET_Invalid ///< Element has invalid type and possible contain invalid data.
};
const EType Type; ///< Type of element.
std::string ID; ///< ID of element.
AMFNodeElementBase *Parent; ///< Parent element. If nullptr then this node is root.
std::list<AMFNodeElementBase *> Child; ///< Child elements.
public:
/// Destructor, virtual..
virtual ~AMFNodeElementBase() = default;
/// Disabled copy constructor and co.
AMFNodeElementBase(const AMFNodeElementBase &pNodeElement) = delete;
AMFNodeElementBase(AMFNodeElementBase &&) = delete;
AMFNodeElementBase &operator=(const AMFNodeElementBase &pNodeElement) = delete;
AMFNodeElementBase() = delete;
protected:
/// In constructor inheritor must set element type.
/// \param [in] type - element type.
/// \param [in] pParent - parent element.
AMFNodeElementBase(EType type, AMFNodeElementBase *pParent) :
Type(type), Parent(pParent) {
// empty
}
}; // class IAMFImporter_NodeElement
/// A collection of objects or constellations with specific relative locations.
struct AMFConstellation final : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFConstellation(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Constellation, pParent) {}
}; // struct CAMFImporter_NodeElement_Constellation
/// Part of constellation.
struct AMFInstance final : public AMFNodeElementBase {
std::string ObjectID; ///< ID of object for instantiation.
/// \var Delta - The distance of translation in the x, y, or z direction, respectively, in the referenced object's coordinate system, to
/// create an instance of the object in the current constellation.
aiVector3D Delta;
/// \var Rotation - The rotation, in degrees, to rotate the referenced object about its x, y, and z axes, respectively, to create an
/// instance of the object in the current constellation. Rotations shall be executed in order of x first, then y, then z.
aiVector3D Rotation;
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFInstance(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Instance, pParent) {}
};
/// Structure that define metadata node.
struct AMFMetadata : public AMFNodeElementBase {
std::string MetaType; ///< Type of "Value".
std::string Value; ///< Value.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFMetadata(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Metadata, pParent) {
// empty
}
};
/// Structure that define root node.
struct AMFRoot : public AMFNodeElementBase {
std::string Unit; ///< The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
std::string Version; ///< Version of format.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFRoot(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Root, pParent) {
// empty
}
};
/// Structure that define object node.
struct AMFColor : public AMFNodeElementBase {
bool Composed; ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color.
std::string Color_Composed[4]; ///< By components formulas of composed color. [0..3] - RGBA.
aiColor4D Color; ///< Constant color.
std::string Profile; ///< The ICC color space used to interpret the three color channels r, g and b..
/// @brief Constructor.
/// @param [in] pParent - pointer to parent node.
explicit AMFColor(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Color, pParent), Composed(false), Color() {
// empty
}
};
/// Structure that define material node.
struct AMFMaterial : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFMaterial(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Material, pParent) {
// empty
}
};
/// Structure that define object node.
struct AMFObject : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFObject(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Object, pParent) {
// empty
}
};
/// Structure that define mesh node.
struct AMFMesh : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFMesh(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Mesh, pParent) {
// empty
}
};
/// Structure that define vertex node.
struct AMFVertex : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFVertex(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Vertex, pParent) {
// empty
}
};
/// Structure that define edge node.
struct AMFEdge : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFEdge(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Edge, pParent) {
// empty
}
};
/// Structure that define vertices node.
struct AMFVertices : public AMFNodeElementBase {
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFVertices(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Vertices, pParent) {
// empty
}
};
/// Structure that define volume node.
struct AMFVolume : public AMFNodeElementBase {
std::string MaterialID; ///< Which material to use.
std::string VolumeType; ///< What this volume describes can be "region" or "support". If none specified, "object" is assumed.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFVolume(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Volume, pParent) {
// empty
}
};
/// Structure that define coordinates node.
struct AMFCoordinates : public AMFNodeElementBase {
aiVector3D Coordinate; ///< Coordinate.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFCoordinates(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Coordinates, pParent) {
// empty
}
};
/// Structure that define texture coordinates node.
struct AMFTexMap : public AMFNodeElementBase {
aiVector3D TextureCoordinate[3]; ///< Texture coordinates.
std::string TextureID_R; ///< Texture ID for red color component.
std::string TextureID_G; ///< Texture ID for green color component.
std::string TextureID_B; ///< Texture ID for blue color component.
std::string TextureID_A; ///< Texture ID for alpha color component.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFTexMap(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_TexMap, pParent), TextureCoordinate{} {
// empty
}
};
/// Structure that define triangle node.
struct AMFTriangle : public AMFNodeElementBase {
size_t V[3]; ///< Triangle vertices.
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFTriangle(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Triangle, pParent) {
// empty
}
};
/// Structure that define texture node.
struct AMFTexture : public AMFNodeElementBase {
size_t Width, Height, Depth; ///< Size of the texture.
std::vector<uint8_t> Data; ///< Data of the texture.
bool Tiled;
/// Constructor.
/// \param [in] pParent - pointer to parent node.
explicit AMFTexture(AMFNodeElementBase *pParent) :
AMFNodeElementBase(ENET_Texture, pParent), Width(0), Height(0), Depth(0), Data(), Tiled(false) {
// empty
}
};
#endif // INCLUDED_AI_AMF_IMPORTER_NODE_H

View File

@@ -0,0 +1,892 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/// \file AMFImporter_Postprocess.cpp
/// \brief Convert built scenegraph and objects to Assimp scenegraph.
/// \date 2016
/// \author smal.root@gmail.com
#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
#include "AMFImporter.hpp"
#include <assimp/SceneCombiner.h>
#include <assimp/StandardShapes.h>
#include <assimp/StringUtils.h>
#include <iterator>
namespace Assimp {
aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const {
// Check if stored data are supported.
if (!Composition.empty()) {
throw DeadlyImportError("IME. GetColor for composition");
}
if (Color->Composed) {
throw DeadlyImportError("IME. GetColor, composed color");
}
aiColor4D tcol = Color->Color;
// Check if default color must be used
if ((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) {
tcol.r = 0.5f;
tcol.g = 0.5f;
tcol.b = 0.5f;
tcol.a = 1;
}
return tcol;
}
void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &nodeElement, std::vector<aiVector3D> &vertexCoordinateArray,
std::vector<AMFColor *> &pVertexColorArray) const {
AMFVertices *vn = nullptr;
size_t col_idx;
// All data stored in "vertices", search for it.
for (AMFNodeElementBase *ne_child : nodeElement.Child) {
if (ne_child->Type == AMFNodeElementBase::ENET_Vertices) {
vn = (AMFVertices*)ne_child;
}
}
// If "vertices" not found then no work for us.
if (vn == nullptr) {
return;
}
// all coordinates stored as child and we need to reserve space for future push_back's.
vertexCoordinateArray.reserve(vn->Child.size());
// colors count equal vertices count.
pVertexColorArray.resize(vn->Child.size());
col_idx = 0;
// Inside vertices collect all data and place to arrays
for (AMFNodeElementBase *vn_child : vn->Child) {
// vertices, colors
if (vn_child->Type == AMFNodeElementBase::ENET_Vertex) {
// by default clear color for current vertex
pVertexColorArray[col_idx] = nullptr;
for (AMFNodeElementBase *vtx : vn_child->Child) {
if (vtx->Type == AMFNodeElementBase::ENET_Coordinates) {
vertexCoordinateArray.push_back(((AMFCoordinates *)vtx)->Coordinate);
continue;
}
if (vtx->Type == AMFNodeElementBase::ENET_Color) {
pVertexColorArray[col_idx] = (AMFColor *)vtx;
continue;
}
}
++col_idx;
}
}
}
size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string &r, const std::string &g, const std::string &b, const std::string &a) {
if (r.empty() && g.empty() && b.empty() && a.empty()) {
throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined.");
}
std::string TextureConverted_ID = r + "_" + g + "_" + b + "_" + a;
size_t TextureConverted_Index = 0;
for (const SPP_Texture &tex_convd : mTexture_Converted) {
if (tex_convd.ID == TextureConverted_ID) {
return TextureConverted_Index;
} else {
++TextureConverted_Index;
}
}
// Converted texture not found, create it.
AMFTexture *src_texture[4] {
nullptr
};
std::vector<AMFTexture *> src_texture_4check;
SPP_Texture converted_texture;
{ // find all specified source textures
AMFNodeElementBase *t_tex = nullptr;
// R
if (!r.empty()) {
if (!Find_NodeElement(r, AMFNodeElementBase::EType::ENET_Texture, &t_tex)) {
Throw_ID_NotFound(r);
}
src_texture[0] = (AMFTexture *)t_tex;
src_texture_4check.push_back((AMFTexture *)t_tex);
} else {
src_texture[0] = nullptr;
}
// G
if (!g.empty()) {
if (!Find_NodeElement(g, AMFNodeElementBase::ENET_Texture, &t_tex)) {
Throw_ID_NotFound(g);
}
src_texture[1] = (AMFTexture *)t_tex;
src_texture_4check.push_back((AMFTexture *)t_tex);
} else {
src_texture[1] = nullptr;
}
// B
if (!b.empty()) {
if (!Find_NodeElement(b, AMFNodeElementBase::ENET_Texture, &t_tex)) {
Throw_ID_NotFound(b);
}
src_texture[2] = (AMFTexture *)t_tex;
src_texture_4check.push_back((AMFTexture *)t_tex);
} else {
src_texture[2] = nullptr;
}
// A
if (!a.empty()) {
if (!Find_NodeElement(a, AMFNodeElementBase::ENET_Texture, &t_tex)) {
Throw_ID_NotFound(a);
}
src_texture[3] = (AMFTexture *)t_tex;
src_texture_4check.push_back((AMFTexture *)t_tex);
} else {
src_texture[3] = nullptr;
}
} // END: find all specified source textures
// check that all textures has same size
if (src_texture_4check.size() > 1) {
for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++) {
if ((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) ||
(src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth)) {
throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size.");
}
}
} // if(src_texture_4check.size() > 1)
// set texture attributes
converted_texture.Width = src_texture_4check[0]->Width;
converted_texture.Height = src_texture_4check[0]->Height;
converted_texture.Depth = src_texture_4check[0]->Depth;
// if one of source texture is tiled then converted texture is tiled too.
converted_texture.Tiled = false;
for (uint8_t i = 0; i < src_texture_4check.size(); ++i) {
converted_texture.Tiled |= src_texture_4check[i]->Tiled;
}
// Create format hint.
constexpr char templateColor[] = "rgba0000";
memcpy(converted_texture.FormatHint, templateColor, 8);
if (!r.empty()) converted_texture.FormatHint[4] = '8';
if (!g.empty()) converted_texture.FormatHint[5] = '8';
if (!b.empty()) converted_texture.FormatHint[6] = '8';
if (!a.empty()) converted_texture.FormatHint[7] = '8';
// Сopy data of textures.
size_t tex_size = 0;
size_t step = 0;
size_t off_g = 0;
size_t off_b = 0;
// Calculate size of the target array and rule how data will be copied.
if (!r.empty() && nullptr != src_texture[0]) {
tex_size += src_texture[0]->Data.size();
step++, off_g++, off_b++;
}
if (!g.empty() && nullptr != src_texture[1]) {
tex_size += src_texture[1]->Data.size();
step++, off_b++;
}
if (!b.empty() && nullptr != src_texture[2]) {
tex_size += src_texture[2]->Data.size();
step++;
}
if (!a.empty() && nullptr != src_texture[3]) {
tex_size += src_texture[3]->Data.size();
step++;
}
// Create target array.
converted_texture.Data = new uint8_t[tex_size];
// And copy data
auto CopyTextureData = [&](const std::string &pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void {
if (!pID.empty()) {
for (size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) {
AMFTexture *tex = src_texture[pSrcTexNum];
ai_assert(tex);
converted_texture.Data[idx_target] = tex->Data.at(idx_src);
}
}
}; // auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
CopyTextureData(r, 0, step, 0);
CopyTextureData(g, off_g, step, 1);
CopyTextureData(b, off_b, step, 2);
CopyTextureData(a, step - 1, step, 3);
// Store new converted texture ID
converted_texture.ID = TextureConverted_ID;
// Store new converted texture
mTexture_Converted.push_back(converted_texture);
return TextureConverted_Index;
}
void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> &pInputList, std::list<std::list<SComplexFace>> &pOutputList_Separated) {
auto texmap_is_equal = [](const AMFTexMap *pTexMap1, const AMFTexMap *pTexMap2) -> bool {
if ((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true;
if (pTexMap1 == nullptr) return false;
if (pTexMap2 == nullptr) return false;
if (pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false;
if (pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false;
if (pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false;
if (pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false;
return true;
};
pOutputList_Separated.clear();
if (pInputList.empty()) return;
do {
SComplexFace face_start = pInputList.front();
std::list<SComplexFace> face_list_cur;
for (std::list<SComplexFace>::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;) {
if (texmap_is_equal(face_start.TexMap, it->TexMap)) {
auto it_old = it;
++it;
face_list_cur.push_back(*it_old);
pInputList.erase(it_old);
} else {
++it;
}
}
if (!face_list_cur.empty()) pOutputList_Separated.push_back(face_list_cur);
} while (!pInputList.empty());
}
void AMFImporter::Postprocess_AddMetadata(const AMFMetaDataArray &metadataList, aiNode &sceneNode) const {
if (metadataList.empty()) {
return;
}
if (sceneNode.mMetaData != nullptr) {
throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong.");
}
// copy collected metadata to output node.
sceneNode.mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(metadataList.size()));
size_t meta_idx(0);
for (const AMFMetadata *metadata : metadataList) {
sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata->MetaType, aiString(metadata->Value));
}
}
void AMFImporter::Postprocess_BuildNodeAndObject(const AMFObject &pNodeElement, MeshArray &meshList, aiNode **pSceneNode) {
AMFColor *object_color = nullptr;
// create new aiNode and set name as <object> has.
*pSceneNode = new aiNode;
(*pSceneNode)->mName = pNodeElement.ID;
// read mesh and color
for (const AMFNodeElementBase *ne_child : pNodeElement.Child) {
std::vector<aiVector3D> vertex_arr;
std::vector<AMFColor *> color_arr;
// color for object
if (ne_child->Type == AMFNodeElementBase::ENET_Color) {
object_color = (AMFColor *) ne_child;
}
if (ne_child->Type == AMFNodeElementBase::ENET_Mesh) {
// Create arrays from children of mesh: vertices.
PostprocessHelper_CreateMeshDataArray(*((AMFMesh *)ne_child), vertex_arr, color_arr);
// Use this arrays as a source when creating every aiMesh
Postprocess_BuildMeshSet(*((AMFMesh *)ne_child), vertex_arr, color_arr, object_color, meshList, **pSceneNode);
}
} // for(const CAMFImporter_NodeElement* ne_child: pNodeElement)
}
void AMFImporter::Postprocess_BuildMeshSet(const AMFMesh &pNodeElement, const std::vector<aiVector3D> &pVertexCoordinateArray,
const std::vector<AMFColor *> &pVertexColorArray, const AMFColor *pObjectColor, MeshArray &pMeshList, aiNode &pSceneNode) {
std::list<unsigned int> mesh_idx;
// all data stored in "volume", search for it.
for (const AMFNodeElementBase *ne_child : pNodeElement.Child) {
const AMFColor *ne_volume_color = nullptr;
const SPP_Material *cur_mat = nullptr;
if (ne_child->Type == AMFNodeElementBase::ENET_Volume) {
/******************* Get faces *******************/
const AMFVolume *ne_volume = reinterpret_cast<const AMFVolume *>(ne_child);
std::list<SComplexFace> complex_faces_list; // List of the faces of the volume.
std::list<std::list<SComplexFace>> complex_faces_toplist; // List of the face list for every mesh.
// check if volume use material
if (!ne_volume->MaterialID.empty()) {
if (!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) {
Throw_ID_NotFound(ne_volume->MaterialID);
}
}
// inside "volume" collect all data and place to arrays or create new objects
for (const AMFNodeElementBase *ne_volume_child : ne_volume->Child) {
// color for volume
if (ne_volume_child->Type == AMFNodeElementBase::ENET_Color) {
ne_volume_color = reinterpret_cast<const AMFColor *>(ne_volume_child);
} else if (ne_volume_child->Type == AMFNodeElementBase::ENET_Triangle) // triangles, triangles colors
{
const AMFTriangle &tri_al = *reinterpret_cast<const AMFTriangle *>(ne_volume_child);
SComplexFace complex_face;
// initialize pointers
complex_face.Color = nullptr;
complex_face.TexMap = nullptr;
// get data from triangle children: color, texture coordinates.
if (tri_al.Child.size()) {
for (const AMFNodeElementBase *ne_triangle_child : tri_al.Child) {
if (ne_triangle_child->Type == AMFNodeElementBase::ENET_Color)
complex_face.Color = reinterpret_cast<const AMFColor *>(ne_triangle_child);
else if (ne_triangle_child->Type == AMFNodeElementBase::ENET_TexMap)
complex_face.TexMap = reinterpret_cast<const AMFTexMap *>(ne_triangle_child);
}
} // if(tri_al.Child.size())
// create new face and store it.
complex_face.Face.mNumIndices = 3;
complex_face.Face.mIndices = new unsigned int[3];
complex_face.Face.mIndices[0] = static_cast<unsigned int>(tri_al.V[0]);
complex_face.Face.mIndices[1] = static_cast<unsigned int>(tri_al.V[1]);
complex_face.Face.mIndices[2] = static_cast<unsigned int>(tri_al.V[2]);
complex_faces_list.push_back(complex_face);
}
} // for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
/**** Split faces list: one list per mesh ****/
PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist);
/***** Create mesh for every faces list ******/
for (std::list<SComplexFace> &face_list_cur : complex_faces_toplist) {
auto VertexIndex_GetMinimal = [](const std::list<SComplexFace> &pFaceList, const size_t *pBiggerThan) -> size_t {
size_t rv = 0;
if (pBiggerThan != nullptr) {
bool found = false;
const size_t biggerThan = *pBiggerThan;
for (const SComplexFace &face : pFaceList) {
for (size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++) {
if (face.Face.mIndices[idx_vert] > biggerThan) {
rv = face.Face.mIndices[idx_vert];
found = true;
break;
}
}
if (found) {
break;
}
}
if (!found) {
return *pBiggerThan;
}
} else {
rv = pFaceList.front().Face.mIndices[0];
} // if(pBiggerThan != nullptr) else
for (const SComplexFace &face : pFaceList) {
for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) {
if (face.Face.mIndices[vi] < rv) {
if (pBiggerThan != nullptr) {
if (face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi];
} else {
rv = face.Face.mIndices[vi];
}
}
}
} // for(const SComplexFace& face: pFaceList)
return rv;
}; // auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
auto VertexIndex_Replace = [](std::list<SComplexFace> &pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void {
for (const SComplexFace &face : pFaceList) {
for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) {
if (face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast<unsigned int>(pIdx_To);
}
}
}; // auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D {
// Color priorities(In descending order):
// 1. triangle color;
// 2. vertex color;
// 3. volume color;
// 4. object color;
// 5. material;
// 6. default - invisible coat.
//
// Fill vertices colors in color priority list above that's points from 1 to 6.
if ((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr)) // check for vertex color
{
if (pVertexColorArray[pIdx]->Composed)
throw DeadlyImportError("IME: vertex color composed");
else
return pVertexColorArray[pIdx]->Color;
} else if (ne_volume_color != nullptr) // check for volume color
{
if (ne_volume_color->Composed)
throw DeadlyImportError("IME: volume color composed");
else
return ne_volume_color->Color;
} else if (pObjectColor != nullptr) // check for object color
{
if (pObjectColor->Composed)
throw DeadlyImportError("IME: object color composed");
else
return pObjectColor->Color;
} else if (cur_mat != nullptr) // check for material
{
return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z);
} else // set default color.
{
return { 0, 0, 0, 0 };
} // if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else
}; // auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
aiMesh *tmesh = new aiMesh;
tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; // Only triangles is supported by AMF.
//
// set geometry and colors (vertices)
//
// copy faces/triangles
tmesh->mNumFaces = static_cast<unsigned int>(face_list_cur.size());
tmesh->mFaces = new aiFace[tmesh->mNumFaces];
// Create vertices list and optimize indices. Optimization mean following.In AMF all volumes use one big list of vertices. And one volume
// can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10.
// Do you need all this thousands of garbage? Of course no. So, optimization step transform sparse indices set to continuous.
size_t VertexCount_Max = tmesh->mNumFaces * 3; // 3 - triangles.
std::vector<aiVector3D> vert_arr, texcoord_arr;
std::vector<aiColor4D> col_arr;
vert_arr.reserve(VertexCount_Max * 2); // "* 2" - see below TODO.
col_arr.reserve(VertexCount_Max * 2);
{ // fill arrays
// first iteration.
size_t vert_idx_to = 0;
size_t vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr);
vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
// rest iterations
do {
vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to);
if (vert_idx_from == vert_idx_to) break; // all indices are transferred,
vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
vert_idx_to++;
if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
} while (true);
} // fill arrays. END.
//
// check if triangle colors are used and create additional faces if needed.
//
for (const SComplexFace &face_cur : face_list_cur) {
if (face_cur.Color != nullptr) {
aiColor4D face_color;
size_t vert_idx_new = vert_arr.size();
if (face_cur.Color->Composed)
throw DeadlyImportError("IME: face color composed");
else
face_color = face_cur.Color->Color;
for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) {
vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind]));
col_arr.push_back(face_color);
face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(vert_idx_new++);
}
} // if(face_cur.Color != nullptr)
} // for(const SComplexFace& face_cur: face_list_cur)
//
// if texture is used then copy texture coordinates too.
//
if (face_list_cur.front().TexMap != nullptr) {
size_t idx_vert_new = vert_arr.size();
///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for
/// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about
/// optimization.
bool *idx_vert_used;
idx_vert_used = new bool[VertexCount_Max * 2];
for (size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++)
idx_vert_used[i] = false;
// This ID's will be used when set materials ID in scene.
tmesh->mMaterialIndex = static_cast<unsigned int>(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R,
face_list_cur.front().TexMap->TextureID_G,
face_list_cur.front().TexMap->TextureID_B,
face_list_cur.front().TexMap->TextureID_A));
texcoord_arr.resize(VertexCount_Max * 2);
for (const SComplexFace &face_cur : face_list_cur) {
for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) {
const size_t idx_vert = face_cur.Face.mIndices[idx_ind];
if (!idx_vert_used[idx_vert]) {
texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind];
idx_vert_used[idx_vert] = true;
} else if (texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind]) {
// in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture
// coordinates.
vert_arr.push_back(vert_arr.at(idx_vert));
col_arr.push_back(col_arr.at(idx_vert));
texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind];
face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(idx_vert_new++);
}
} // for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
} // for(const SComplexFace& face_cur: face_list_cur)
delete[] idx_vert_used;
// shrink array
texcoord_arr.resize(idx_vert_new);
} // if(face_list_cur.front().TexMap != nullptr)
//
// copy collected data to mesh
//
tmesh->mNumVertices = static_cast<unsigned int>(vert_arr.size());
tmesh->mVertices = new aiVector3D[tmesh->mNumVertices];
tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices];
memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D));
if (texcoord_arr.size() > 0) {
tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices];
memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
tmesh->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D.
}
size_t idx_face = 0;
for (const SComplexFace &face_cur : face_list_cur)
tmesh->mFaces[idx_face++] = face_cur.Face;
// store new aiMesh
mesh_idx.push_back(static_cast<unsigned int>(pMeshList.size()));
pMeshList.push_back(tmesh);
} // for(const std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
} // if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
} // for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
// if meshes was created then assign new indices with current aiNode
if (!mesh_idx.empty()) {
std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
pSceneNode.mNumMeshes = static_cast<unsigned int>(mesh_idx.size());
pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes];
for (size_t i = 0; i < pSceneNode.mNumMeshes; i++)
pSceneNode.mMeshes[i] = *mit++;
} // if(mesh_idx.size() > 0)
}
void AMFImporter::Postprocess_BuildMaterial(const AMFMaterial &pMaterial) {
SPP_Material new_mat;
new_mat.ID = pMaterial.ID;
for (const AMFNodeElementBase *mat_child : pMaterial.Child) {
if (mat_child->Type == AMFNodeElementBase::ENET_Color) {
new_mat.Color = (AMFColor*)mat_child;
} else if (mat_child->Type == AMFNodeElementBase::ENET_Metadata) {
new_mat.Metadata.push_back((AMFMetadata *)mat_child);
}
} // for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child)
// place converted material to special list
mMaterial_Converted.push_back(new_mat);
}
void AMFImporter::Postprocess_BuildConstellation(AMFConstellation &pConstellation, NodeArray &nodeArray) const {
aiNode *con_node;
std::list<aiNode *> ch_node;
// We will build next hierarchy:
// aiNode as parent (<constellation>) for set of nodes as a children
// |- aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
// ...
// \_ aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
con_node = new aiNode;
con_node->mName = pConstellation.ID;
// Walk through children and search for instances of another objects, constellations.
for (const AMFNodeElementBase *ne : pConstellation.Child) {
aiMatrix4x4 tmat;
aiNode *t_node;
aiNode *found_node;
if (ne->Type == AMFNodeElementBase::ENET_Metadata) continue;
if (ne->Type != AMFNodeElementBase::ENET_Instance) throw DeadlyImportError("Only <instance> nodes can be in <constellation>.");
// create alias for convenience
AMFInstance &als = *((AMFInstance *)ne);
// find referenced object
if (!Find_ConvertedNode(als.ObjectID, nodeArray, &found_node)) Throw_ID_NotFound(als.ObjectID);
// create node for applying transformation
t_node = new aiNode;
t_node->mParent = con_node;
// apply transformation
aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat;
aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat;
aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat;
aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat;
// create array for one child node
t_node->mNumChildren = 1;
t_node->mChildren = new aiNode *[t_node->mNumChildren];
SceneCombiner::Copy(&t_node->mChildren[0], found_node);
t_node->mChildren[0]->mParent = t_node;
ch_node.push_back(t_node);
} // for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
// copy found aiNode's as children
if (ch_node.empty()) throw DeadlyImportError("<constellation> must have at least one <instance>.");
size_t ch_idx = 0;
con_node->mNumChildren = static_cast<unsigned int>(ch_node.size());
con_node->mChildren = new aiNode *[con_node->mNumChildren];
for (aiNode *node : ch_node)
con_node->mChildren[ch_idx++] = node;
// and place "root" of <constellation> node to node list
nodeArray.push_back(con_node);
}
void AMFImporter::Postprocess_BuildScene(aiScene *pScene) {
NodeArray nodeArray;
MeshArray mesh_list;
AMFMetaDataArray meta_list;
//
// Because for AMF "material" is just complex colors mixing so aiMaterial will not be used.
// For building aiScene we are must to do few steps:
// at first creating root node for aiScene.
pScene->mRootNode = new aiNode;
pScene->mRootNode->mParent = nullptr;
pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
// search for root(<amf>) element
AMFNodeElementBase *root_el = nullptr;
for (AMFNodeElementBase *ne : mNodeElement_List) {
if (ne->Type != AMFNodeElementBase::ENET_Root) {
continue;
}
root_el = ne;
break;
} // for(const CAMFImporter_NodeElement* ne: mNodeElement_List)
// Check if root element are found.
if (root_el == nullptr) {
throw DeadlyImportError("Root(<amf>) element not found.");
}
// after that walk through children of root and collect data. Five types of nodes can be placed at top level - in <amf>: <object>, <material>, <texture>,
// <constellation> and <metadata>. But at first we must read <material> and <texture> because they will be used in <object>. <metadata> can be read
// at any moment.
//
// 1. <material>
// 2. <texture> will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet
for (const AMFNodeElementBase *root_child : root_el->Child) {
if (root_child->Type == AMFNodeElementBase::ENET_Material) {
Postprocess_BuildMaterial(*((AMFMaterial *)root_child));
}
}
// After "appearance" nodes we must read <object> because it will be used in <constellation> -> <instance>.
//
// 3. <object>
for (const AMFNodeElementBase *root_child : root_el->Child) {
if (root_child->Type == AMFNodeElementBase::ENET_Object) {
aiNode *tnode = nullptr;
// for <object> mesh and node must be built: object ID assigned to aiNode name and will be used in future for <instance>
Postprocess_BuildNodeAndObject(*((AMFObject *)root_child), mesh_list, &tnode);
if (tnode != nullptr) {
nodeArray.push_back(tnode);
}
}
} // for(const CAMFImporter_NodeElement* root_child: root_el->Child)
// And finally read rest of nodes.
//
for (const AMFNodeElementBase *root_child : root_el->Child) {
// 4. <constellation>
if (root_child->Type == AMFNodeElementBase::ENET_Constellation) {
// <object> and <constellation> at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's.
Postprocess_BuildConstellation(*((AMFConstellation *)root_child), nodeArray);
}
// 5, <metadata>
if (root_child->Type == AMFNodeElementBase::ENET_Metadata) meta_list.push_back((AMFMetadata *)root_child);
} // for(const CAMFImporter_NodeElement* root_child: root_el->Child)
// at now we can add collected metadata to root node
Postprocess_AddMetadata(meta_list, *pScene->mRootNode);
//
// Check constellation children
//
// As said in specification:
// "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing."
// What that means? For example: if some object is used in constellation then you must show only constellation but not original object.
// And at this step we are checking that relations.
nl_clean_loop:
if (nodeArray.size() > 1) {
// walk through all nodes
for (NodeArray::iterator nl_it = nodeArray.begin(); nl_it != nodeArray.end(); ++nl_it) {
// and try to find them in another top nodes.
NodeArray::const_iterator next_it = nl_it;
++next_it;
for (; next_it != nodeArray.end(); ++next_it) {
if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) {
// if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
// FIXME: this leaks memory on test models test8.amf and test9.amf
nodeArray.erase(nl_it);
goto nl_clean_loop;
}
} // for(; next_it != node_list.end(); next_it++)
} // for(std::list<aiNode*>::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
}
//
// move created objects to aiScene
//
//
// Nodes
if (!nodeArray.empty()) {
NodeArray::const_iterator nl_it = nodeArray.begin();
pScene->mRootNode->mNumChildren = static_cast<unsigned int>(nodeArray.size());
pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
for (size_t i = 0; i < pScene->mRootNode->mNumChildren; i++) {
// Objects and constellation that must be showed placed at top of hierarchy in <amf> node. So all aiNode's in node_list must have
// mRootNode only as parent.
(*nl_it)->mParent = pScene->mRootNode;
pScene->mRootNode->mChildren[i] = *nl_it++;
}
} // if(node_list.size() > 0)
//
// Meshes
if (!mesh_list.empty()) {
MeshArray::const_iterator ml_it = mesh_list.begin();
pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size());
pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
for (size_t i = 0; i < pScene->mNumMeshes; i++)
pScene->mMeshes[i] = *ml_it++;
} // if(mesh_list.size() > 0)
//
// Textures
pScene->mNumTextures = static_cast<unsigned int>(mTexture_Converted.size());
if (pScene->mNumTextures > 0) {
size_t idx;
idx = 0;
pScene->mTextures = new aiTexture *[pScene->mNumTextures];
for (const SPP_Texture &tex_convd : mTexture_Converted) {
pScene->mTextures[idx] = new aiTexture;
pScene->mTextures[idx]->mWidth = static_cast<unsigned int>(tex_convd.Width);
pScene->mTextures[idx]->mHeight = static_cast<unsigned int>(tex_convd.Height);
pScene->mTextures[idx]->pcData = (aiTexel *)tex_convd.Data;
// texture format description.
strncpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint, HINTMAXTEXTURELEN);
idx++;
} // for(const SPP_Texture& tex_convd: mTexture_Converted)
// Create materials for embedded textures.
idx = 0;
pScene->mNumMaterials = static_cast<unsigned int>(mTexture_Converted.size());
pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
for (const SPP_Texture &tex_convd : mTexture_Converted) {
const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + ai_to_string(idx));
const int mode = aiTextureOp_Multiply;
const int repeat = tex_convd.Tiled ? 1 : 0;
pScene->mMaterials[idx] = new aiMaterial;
pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0));
pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0));
pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
idx++;
}
} // if(pScene->mNumTextures > 0)
} // END: after that walk through children of root and collect data
} // namespace Assimp
#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file ASELoader.h
* @brief Definition of the .ASE importer class.
*/
#ifndef AI_ASELOADER_H_INCLUDED
#define AI_ASELOADER_H_INCLUDED
#include <assimp/BaseImporter.h>
#include <assimp/types.h>
#include "ASEParser.h"
struct aiNode;
namespace Assimp {
#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
// --------------------------------------------------------------------------------
/** Importer class for the 3DS ASE ASCII format.
*
*/
class ASEImporter final : public BaseImporter {
public:
ASEImporter();
~ASEImporter() override = default;
// -------------------------------------------------------------------
/** Returns whether the class can handle the format of the given file.
* See BaseImporter::CanRead() for details.
*/
bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
bool checkSig) const override;
protected:
// -------------------------------------------------------------------
/** Return importer meta information.
* See #BaseImporter::GetInfo for the details
*/
const aiImporterDesc* GetInfo () const override;
// -------------------------------------------------------------------
/** Imports the given file into the given scene structure.
* See BaseImporter::InternReadFile() for details
*/
void InternReadFile( const std::string& pFile, aiScene* pScene,
IOSystem* pIOHandler) override;
// -------------------------------------------------------------------
/** Called prior to ReadFile().
* The function is a request to the importer to update its configuration
* basing on the Importer's configuration property list.
*/
void SetupProperties(const Importer* pImp) override;
private:
// -------------------------------------------------------------------
/** Generate normal vectors basing on smoothing groups
* (in some cases the normal are already contained in the file)
* \param mesh Mesh to work on
* \return false if the normals have been recomputed
*/
bool GenerateNormals(ASE::Mesh& mesh);
// -------------------------------------------------------------------
/** Create valid vertex/normal/UV/color/face lists.
* All elements are unique, faces have only one set of indices
* after this step occurs.
* \param mesh Mesh to work on
*/
void BuildUniqueRepresentation(ASE::Mesh& mesh);
/** Create one-material-per-mesh meshes ;-)
* \param mesh Mesh to work with
* \param Receives the list of all created meshes
*/
void ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOut);
// -------------------------------------------------------------------
/** Convert a material to a aiMaterial object
* \param mat Input material
*/
void ConvertMaterial(ASE::Material& mat);
// -------------------------------------------------------------------
/** Setup the final material indices for each mesh
*/
void BuildMaterialIndices();
// -------------------------------------------------------------------
/** Build the node graph
*/
void BuildNodes(std::vector<ASE::BaseNode*>& nodes);
// -------------------------------------------------------------------
/** Build output cameras
*/
void BuildCameras();
// -------------------------------------------------------------------
/** Build output lights
*/
void BuildLights();
// -------------------------------------------------------------------
/** Build output animations
*/
void BuildAnimations(const std::vector<ASE::BaseNode*>& nodes);
// -------------------------------------------------------------------
/** Add sub nodes to a node
* \param pcParent parent node to be filled
* \param szName Name of the parent node
* \param matrix Current transform
*/
void AddNodes(const std::vector<ASE::BaseNode*>& nodes,
aiNode* pcParent, const std::string &name);
void AddNodes(const std::vector<ASE::BaseNode*>& nodes,
aiNode* pcParent, const std::string &name,
const aiMatrix4x4& matrix);
void AddMeshes(const ASE::BaseNode* snode, aiNode* node);
// -------------------------------------------------------------------
/** Generate a default material and add it to the parser's list
* Called if no material has been found in the file (rare for ASE,
* but not impossible)
*/
void GenerateDefaultMaterial();
protected:
/** Parser instance */
ASE::Parser* mParser;
/** Buffer to hold the loaded file */
char* mBuffer;
/** Scene to be filled */
aiScene* pcScene;
/** Config options: Recompute the normals in every case - WA
for 3DS Max broken ASE normal export */
bool configRecomputeNormals;
bool noSkeletonMesh;
};
#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
} // end of namespace Assimp
#endif // AI_3DSIMPORTER_H_INC

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,674 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file Defines the helper data structures for importing ASE files */
#ifndef AI_ASEFILEHELPER_H_INC
#define AI_ASEFILEHELPER_H_INC
// public ASSIMP headers
#include <assimp/anim.h>
#include <assimp/mesh.h>
#include <assimp/types.h>
#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
// for some helper routines like IsSpace()
#include <assimp/ParsingUtils.h>
#include <assimp/qnan.h>
// ASE is quite similar to 3ds. We can reuse some structures
#include "AssetLib/3DS/3DSLoader.h"
namespace Assimp::ASE {
using namespace D3DS;
// ---------------------------------------------------------------------------
/** Helper structure representing an ASE material */
struct Material final : D3DS::Material {
//! Default constructor has been deleted
Material() = delete;
//! Constructor with explicit name
explicit Material(const std::string &name) :
D3DS::Material(name),
pcInstance(nullptr),
bNeed(false) {
// empty
}
Material(const Material &other) = default;
Material &operator=(const Material &other) {
if (this == &other) {
return *this;
}
avSubMaterials = other.avSubMaterials;
pcInstance = other.pcInstance;
bNeed = other.bNeed;
return *this;
}
//! Move constructor. This is explicitly written because MSVC doesn't support defaulting it
Material(Material &&other) AI_NO_EXCEPT
: D3DS::Material(std::move(other)),
avSubMaterials(std::move(other.avSubMaterials)),
pcInstance(other.pcInstance),
bNeed(other.bNeed) {
other.pcInstance = nullptr;
}
Material &operator=(Material &&other) AI_NO_EXCEPT {
if (this == &other) {
return *this;
}
//D3DS::Material::operator=(std::move(other));
avSubMaterials = std::move(other.avSubMaterials);
pcInstance = other.pcInstance;
bNeed = other.bNeed;
other.pcInstance = nullptr;
return *this;
}
~Material() override = default;
//! Contains all sub materials of this material
std::vector<Material> avSubMaterials;
//! aiMaterial object
aiMaterial *pcInstance;
//! Can we remove this material?
bool bNeed;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE file face */
struct Face : public FaceWithSmoothingGroup {
//! Default constructor. Initializes everything with 0
Face() AI_NO_EXCEPT
: iMaterial(DEFAULT_MATINDEX),
iFace(0) {
// empty
}
//! special value to indicate that no material index has
//! been assigned to a face. The default material index
//! will replace this value later.
static const unsigned int DEFAULT_MATINDEX = 0xFFFFFFFF;
//! Indices into each list of texture coordinates
unsigned int amUVIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS][3];
//! Index into the list of vertex colors
unsigned int mColorIndices[3];
//! (Sub)Material index to be assigned to this face
unsigned int iMaterial;
//! Index of the face. It is not specified whether it is
//! a requirement of the file format that all faces are
//! written in sequential order, so we have to expect this case
unsigned int iFace;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE file bone */
struct Bone {
//! Constructor
Bone() = delete;
//! Construction from an existing name
explicit Bone(const std::string &name) :
mName(name) {
// empty
}
//! Name of the bone
std::string mName;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE file bone vertex */
struct BoneVertex {
//! Bone and corresponding vertex weight.
//! -1 for unrequired bones ....
std::vector<std::pair<int, float>> mBoneWeights;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE file animation */
struct Animation {
enum Type {
TRACK = 0x0,
BEZIER = 0x1,
TCB = 0x2
} mRotationType,
mScalingType, mPositionType;
Animation() AI_NO_EXCEPT
: mRotationType(TRACK),
mScalingType(TRACK),
mPositionType(TRACK) {
// empty
}
//! List of track rotation keyframes
std::vector<aiQuatKey> akeyRotations;
//! List of track position keyframes
std::vector<aiVectorKey> akeyPositions;
//! List of track scaling keyframes
std::vector<aiVectorKey> akeyScaling;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent the inheritance information of an ASE node */
struct InheritanceInfo {
//! Default constructor
InheritanceInfo() AI_NO_EXCEPT {
for (size_t i = 0; i < 3; ++i) {
abInheritPosition[i] = abInheritRotation[i] = abInheritScaling[i] = true;
}
}
//! Inherit the parent's position?, axis order is x,y,z
bool abInheritPosition[3];
//! Inherit the parent's rotation?, axis order is x,y,z
bool abInheritRotation[3];
//! Inherit the parent's scaling?, axis order is x,y,z
bool abInheritScaling[3];
};
// ---------------------------------------------------------------------------
/** Represents an ASE file node. Base class for mesh, light and cameras */
struct BaseNode {
enum Type {
Light,
Camera,
Mesh,
Dummy
} mType;
//! Construction from an existing name
BaseNode(Type _mType, const std::string &name) :
mType(_mType), mName(name), mProcessed(false) {
// Set mTargetPosition to qnan
const ai_real qnan = get_qnan();
mTargetPosition.x = qnan;
}
//! Name of the mesh
std::string mName;
//! Name of the parent of the node
//! "" if there is no parent ...
std::string mParent;
//! Transformation matrix of the node
aiMatrix4x4 mTransform;
//! Target position (target lights and cameras)
aiVector3D mTargetPosition;
//! Specifies which axes transformations a node inherits
//! from its parent ...
InheritanceInfo inherit;
//! Animation channels for the node
Animation mAnim;
//! Needed for lights and cameras: target animation channel
//! Should contain position keys only.
Animation mTargetAnim;
bool mProcessed;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE file mesh */
struct Mesh : public MeshWithSmoothingGroups<ASE::Face>, public BaseNode {
//! Default constructor has been deleted
Mesh() = delete;
//! Construction from an existing name
explicit Mesh(const std::string &name) :
BaseNode(BaseNode::Mesh, name), mVertexColors(), mBoneVertices(), mBones(), iMaterialIndex(Face::DEFAULT_MATINDEX), bSkip(false) {
for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) {
this->mNumUVComponents[c] = 2;
}
}
//! List of all texture coordinate sets
std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
//! List of all vertex color sets.
std::vector<aiColor4D> mVertexColors;
//! List of all bone vertices
std::vector<BoneVertex> mBoneVertices;
//! List of all bones
std::vector<Bone> mBones;
//! Material index of the mesh
unsigned int iMaterialIndex;
//! Number of vertex components for each UVW set
unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
//! used internally
bool bSkip;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE light source */
struct Light : public BaseNode {
enum LightType {
OMNI,
TARGET,
FREE,
DIRECTIONAL
};
//! Default constructor has been deleted
Light() = delete;
//! Construction from an existing name
explicit Light(const std::string &name) :
BaseNode(BaseNode::Light, name), mLightType(OMNI), mColor(1.f, 1.f, 1.f), mIntensity(1.f) // light is white by default
,
mAngle(45.f),
mFalloff(0.f) {
}
LightType mLightType;
aiColor3D mColor;
ai_real mIntensity;
ai_real mAngle; // in degrees
ai_real mFalloff;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE camera */
struct Camera : public BaseNode {
enum CameraType {
FREE,
TARGET
};
//! Default constructor has been deleted
Camera() = delete;
//! Construction from an existing name
explicit Camera(const std::string &name) :
BaseNode(BaseNode::Camera, name), mFOV(0.75f) // in radians
,
mNear(0.1f),
mFar(1000.f) // could be zero
,
mCameraType(FREE) {
}
ai_real mFOV, mNear, mFar;
CameraType mCameraType;
};
// ---------------------------------------------------------------------------
/** Helper structure to represent an ASE helper object (dummy) */
struct Dummy : public BaseNode {
//! Constructor
Dummy() AI_NO_EXCEPT
: BaseNode(BaseNode::Dummy, "DUMMY") {
// empty
}
};
// Parameters to Parser::Parse()
static constexpr unsigned int AI_ASE_NEW_FILE_FORMAT = 200;
static constexpr unsigned int AI_ASE_OLD_FILE_FORMAT = 110;
// Internally we're a little bit more tolerant
#define AI_ASE_IS_NEW_FILE_FORMAT() (iFileFormat >= 200)
#define AI_ASE_IS_OLD_FILE_FORMAT() (iFileFormat < 200)
// -------------------------------------------------------------------------------
/** \brief Class to parse ASE files
*/
class Parser {
public:
/// @brief No default constructor.
Parser() = delete;
// -------------------------------------------------------------------
//! Construct a parser from a given input file which is
//! guaranteed to be terminated with zero.
//! @param file The name of the input file.
//! @param fileFormatDefault Assumed file format version. If the
//! file format is specified in the file the new value replaces
//! the default value.
Parser(const char *file, size_t fileLen, unsigned int fileFormatDefault);
// -------------------------------------------------------------------
//! Parses the file into the parsers internal representation
void Parse();
private:
// -------------------------------------------------------------------
//! Parse the *SCENE block in a file
void ParseLV1SceneBlock();
// -------------------------------------------------------------------
//! Parse the *MESH_SOFTSKINVERTS block in a file
void ParseLV1SoftSkinBlock();
// -------------------------------------------------------------------
//! Parse the *MATERIAL_LIST block in a file
void ParseLV1MaterialListBlock();
// -------------------------------------------------------------------
//! Parse a *<xxx>OBJECT block in a file
//! \param mesh Node to be filled
void ParseLV1ObjectBlock(BaseNode &mesh);
// -------------------------------------------------------------------
//! Parse a *MATERIAL blocks in a material list
//! \param mat Material structure to be filled
void ParseLV2MaterialBlock(Material &mat);
// -------------------------------------------------------------------
//! Parse a *NODE_TM block in a file
//! \param mesh Node (!) object to be filled
void ParseLV2NodeTransformBlock(BaseNode &mesh);
// -------------------------------------------------------------------
//! Parse a *TM_ANIMATION block in a file
//! \param mesh Mesh object to be filled
void ParseLV2AnimationBlock(BaseNode &mesh);
void ParseLV3PosAnimationBlock(ASE::Animation &anim);
void ParseLV3ScaleAnimationBlock(ASE::Animation &anim);
void ParseLV3RotAnimationBlock(ASE::Animation &anim);
// -------------------------------------------------------------------
//! Parse a *MESH block in a file
//! \param mesh Mesh object to be filled
void ParseLV2MeshBlock(Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *LIGHT_SETTINGS block in a file
//! \param light Light object to be filled
void ParseLV2LightSettingsBlock(Light &light);
// -------------------------------------------------------------------
//! Parse a *CAMERA_SETTINGS block in a file
//! \param cam Camera object to be filled
void ParseLV2CameraSettingsBlock(Camera &cam);
// -------------------------------------------------------------------
//! Parse the *MAP_XXXXXX blocks in a material
//! \param map Texture structure to be filled
void ParseLV3MapBlock(Texture &map);
// -------------------------------------------------------------------
//! Parse a *MESH_VERTEX_LIST block in a file
//! \param iNumVertices Value of *MESH_NUMVERTEX, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
void ParseLV3MeshVertexListBlock(
unsigned int iNumVertices, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_FACE_LIST block in a file
//! \param iNumFaces Value of *MESH_NUMFACES, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
void ParseLV3MeshFaceListBlock(
unsigned int iNumFaces, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_TVERT_LIST block in a file
//! \param iNumVertices Value of *MESH_NUMTVERTEX, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
//! \param iChannel Output UVW channel
void ParseLV3MeshTListBlock(
unsigned int iNumVertices, Mesh &mesh, unsigned int iChannel = 0);
// -------------------------------------------------------------------
//! Parse a *MESH_TFACELIST block in a file
//! \param iNumFaces Value of *MESH_NUMTVFACES, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
//! \param iChannel Output UVW channel
void ParseLV3MeshTFaceListBlock(
unsigned int iNumFaces, Mesh &mesh, unsigned int iChannel = 0);
// -------------------------------------------------------------------
//! Parse an additional mapping channel
//! (specified via *MESH_MAPPINGCHANNEL)
//! \param iChannel Channel index to be filled
//! \param mesh Mesh object to be filled
void ParseLV3MappingChannel(
unsigned int iChannel, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_CVERTLIST block in a file
//! \param iNumVertices Value of *MESH_NUMCVERTEX, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
void ParseLV3MeshCListBlock(
unsigned int iNumVertices, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_CFACELIST block in a file
//! \param iNumFaces Value of *MESH_NUMCVFACES, if present.
//! Otherwise zero. This is used to check the consistency of the file.
//! A warning is sent to the logger if the validations fails.
//! \param mesh Mesh object to be filled
void ParseLV3MeshCFaceListBlock(
unsigned int iNumFaces, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_NORMALS block in a file
//! \param mesh Mesh object to be filled
void ParseLV3MeshNormalListBlock(Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_WEIGHTSblock in a file
//! \param mesh Mesh object to be filled
void ParseLV3MeshWeightsBlock(Mesh &mesh);
// -------------------------------------------------------------------
//! Parse the bone list of a file
//! \param mesh Mesh object to be filled
//! \param iNumBones Number of bones in the mesh
void ParseLV4MeshBones(unsigned int iNumBones, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse the bone vertices list of a file
//! \param mesh Mesh object to be filled
//! \param iNumVertices Number of vertices to be parsed
void ParseLV4MeshBonesVertices(unsigned int iNumVertices, Mesh &mesh);
// -------------------------------------------------------------------
//! Parse a *MESH_FACE block in a file
//! \param out receive the face data
void ParseLV4MeshFace(ASE::Face &out);
// -------------------------------------------------------------------
//! Parse a *MESH_VERT block in a file
//! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...)
//! \param apOut Output buffer (3 floats)
//! \param rIndexOut Output index
void ParseLV4MeshRealTriple(ai_real *apOut, unsigned int &rIndexOut);
void ParseLV4MeshFloatTriple(float *apOut, unsigned int &rIndexOut);
// -------------------------------------------------------------------
//! Parse a *MESH_VERT block in a file
//! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...)
//! \param apOut Output buffer (3 floats)
void ParseLV4MeshRealTriple(ai_real *apOut);
void ParseLV4MeshFloatTriple(float *apOut);
// -------------------------------------------------------------------
//! Parse a *MESH_TFACE block in a file
//! (also works for MESH_CFACE)
//! \param apOut Output buffer (3 ints)
//! \param rIndexOut Output index
void ParseLV4MeshLongTriple(unsigned int *apOut, unsigned int &rIndexOut);
// -------------------------------------------------------------------
//! Parse a *MESH_TFACE block in a file
//! (also works for MESH_CFACE)
//! \param apOut Output buffer (3 ints)
void ParseLV4MeshLongTriple(unsigned int *apOut);
// -------------------------------------------------------------------
//! Parse a single float element
//! \param fOut Output float
void ParseLV4MeshReal(ai_real &fOut);
void ParseLV4MeshFloat(float &fOut);
// -------------------------------------------------------------------
//! Parse a single int element
//! \param iOut Output integer
void ParseLV4MeshLong(unsigned int &iOut);
// -------------------------------------------------------------------
//! Skip everything to the next: '*' or '\0'
bool SkipToNextToken();
// -------------------------------------------------------------------
//! Skip the current section until the token after the closing }.
//! This function handles embedded subsections correctly
bool SkipSection();
// -------------------------------------------------------------------
//! Output a warning to the logger
//! \param szWarn Warn message
void LogWarning(const char *szWarn);
// -------------------------------------------------------------------
//! Output a message to the logger
//! \param szWarn Message
void LogInfo(const char *szWarn);
// -------------------------------------------------------------------
//! Output an error to the logger
//! \param szWarn Error message
AI_WONT_RETURN void LogError(const char *szWarn) AI_WONT_RETURN_SUFFIX;
// -------------------------------------------------------------------
//! Parse a string, enclosed in double quotation marks
//! \param out Output string
//! \param szName Name of the enclosing element -> used in error
//! messages.
//! \return false if an error occurred
bool ParseString(std::string &out, const char *szName);
public:
const char *mFilePtr; ////< Pointer to current data
const char *mEnd; ///< The end pointer of the file data
//! background color to be passed to the viewer
//! QNAN if none was found
aiColor3D m_clrBackground;
//! Base ambient color to be passed to all materials
//! QNAN if none was found
aiColor3D m_clrAmbient;
//! List of all materials found in the file
std::vector<Material> m_vMaterials;
//! List of all meshes found in the file
std::vector<Mesh> m_vMeshes;
//! List of all dummies found in the file
std::vector<Dummy> m_vDummies;
//! List of all lights found in the file
std::vector<Light> m_vLights;
//! List of all cameras found in the file
std::vector<Camera> m_vCameras;
//! Current line in the file
unsigned int iLineNumber;
//! First frame
unsigned int iFirstFrame;
//! Last frame
unsigned int iLastFrame;
//! Frame speed - frames per second
unsigned int iFrameSpeed;
//! Ticks per frame
unsigned int iTicksPerFrame;
//! true if the last character read was an end-line character
bool bLastWasEndLine;
//! File format version
unsigned int iFileFormat;
};
} // Namespace Assimp::ASE
#endif // ASSIMP_BUILD_NO_3DS_IMPORTER
#endif // !! include guard

View File

@@ -0,0 +1,68 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssbinExporter.cpp
* ASSBIN exporter main code
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
#include "AssbinFileWriter.h"
#include <assimp/scene.h>
#include <assimp/Exporter.hpp>
#include <assimp/IOSystem.hpp>
namespace Assimp {
void ExportSceneAssbin(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
DumpSceneToAssbin(
pFile,
"\0", // no command(s).
pIOSystem,
pScene,
false, // shortened?
false); // compressed?
}
} // end of namespace Assimp
#endif // ASSIMP_BUILD_NO_ASSBIN_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,60 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssbinExporter.h
* ASSBIN Exporter Main Header
*/
#pragma once
#ifndef AI_ASSBINEXPORTER_H_INC
#define AI_ASSBINEXPORTER_H_INC
#include <assimp/defs.h>
#ifndef ASSIMP_BUILD_NO_EXPORT
// nothing really needed here - reserved for future use like properties
namespace Assimp {
void ASSIMP_API ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/);
}
#endif
#endif // AI_ASSBINEXPORTER_H_INC

View File

@@ -0,0 +1,833 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssbinFileWriter.cpp
* @brief Implementation of Assbin file writer.
*/
#include "AssbinFileWriter.h"
#include "Common/assbin_chunks.h"
#include "PostProcessing/ProcessHelper.h"
#include <assimp/Exceptional.h>
#include <assimp/version.h>
#include <assimp/IOStream.hpp>
#include "zlib.h"
#include <ctime>
#if _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4706)
#endif // _MSC_VER
namespace Assimp {
template <typename T>
size_t Write(IOStream *stream, const T &v) {
return stream->Write(&v, sizeof(T), 1);
}
// -----------------------------------------------------------------------------------
// Serialize an aiString
template <>
inline size_t Write<aiString>(IOStream *stream, const aiString &s) {
const size_t s2 = (uint32_t)s.length;
stream->Write(&s, 4, 1);
stream->Write(s.data, s2, 1);
return s2 + 4;
}
// -----------------------------------------------------------------------------------
// Serialize an unsigned int as uint32_t
template <>
inline size_t Write<unsigned int>(IOStream *stream, const unsigned int &w) {
const uint32_t t = (uint32_t)w;
if (w > t) {
// this shouldn't happen, integers in Assimp data structures never exceed 2^32
throw DeadlyExportError("loss of data due to 64 -> 32 bit integer conversion");
}
stream->Write(&t, 4, 1);
return 4;
}
// -----------------------------------------------------------------------------------
// Serialize an unsigned int as uint16_t
template <>
inline size_t Write<uint16_t>(IOStream *stream, const uint16_t &w) {
static_assert(sizeof(uint16_t) == 2, "sizeof(uint16_t)==2");
stream->Write(&w, 2, 1);
return 2;
}
// -----------------------------------------------------------------------------------
// Serialize a float
template <>
inline size_t Write<float>(IOStream *stream, const float &f) {
static_assert(sizeof(float) == 4, "sizeof(float)==4");
stream->Write(&f, 4, 1);
return 4;
}
// -----------------------------------------------------------------------------------
// Serialize a double
template <>
inline size_t Write<double>(IOStream *stream, const double &f) {
static_assert(sizeof(double) == 8, "sizeof(double)==8");
stream->Write(&f, 8, 1);
return 8;
}
// -----------------------------------------------------------------------------------
// Serialize a vec3
template <>
inline size_t Write<aiVector3D>(IOStream *stream, const aiVector3D &v) {
size_t t = Write<ai_real>(stream, v.x);
t += Write<float>(stream, v.y);
t += Write<float>(stream, v.z);
return t;
}
// -----------------------------------------------------------------------------------
// Serialize a color value
template <>
inline size_t Write<aiColor3D>(IOStream *stream, const aiColor3D &v) {
size_t t = Write<ai_real>(stream, v.r);
t += Write<float>(stream, v.g);
t += Write<float>(stream, v.b);
return t;
}
// -----------------------------------------------------------------------------------
// Serialize a color value
template <>
inline size_t Write<aiColor4D>(IOStream *stream, const aiColor4D &v) {
size_t t = Write<ai_real>(stream, v.r);
t += Write<float>(stream, v.g);
t += Write<float>(stream, v.b);
t += Write<float>(stream, v.a);
return t;
}
// -----------------------------------------------------------------------------------
// Serialize a quaternion
template <>
inline size_t Write<aiQuaternion>(IOStream *stream, const aiQuaternion &v) {
size_t t = Write<ai_real>(stream, v.w);
t += Write<float>(stream, v.x);
t += Write<float>(stream, v.y);
t += Write<float>(stream, v.z);
ai_assert(t == 16);
return t;
}
// -----------------------------------------------------------------------------------
// Serialize a vertex weight
template <>
inline size_t Write<aiVertexWeight>(IOStream *stream, const aiVertexWeight &v) {
size_t t = Write<unsigned int>(stream, v.mVertexId);
return t + Write<float>(stream, v.mWeight);
}
constexpr size_t MatrixSize = 64;
// -----------------------------------------------------------------------------------
// Serialize a mat4x4
template <>
inline size_t Write<aiMatrix4x4>(IOStream *stream, const aiMatrix4x4 &m) {
for (unsigned int i = 0; i < 4; ++i) {
for (unsigned int i2 = 0; i2 < 4; ++i2) {
Write<ai_real>(stream, m[i][i2]);
}
}
return MatrixSize;
}
// -----------------------------------------------------------------------------------
// Serialize an aiVectorKey
template <>
inline size_t Write<aiVectorKey>(IOStream *stream, const aiVectorKey &v) {
const size_t t = Write<double>(stream, v.mTime);
return t + Write<aiVector3D>(stream, v.mValue);
}
// -----------------------------------------------------------------------------------
// Serialize an aiQuatKey
template <>
inline size_t Write<aiQuatKey>(IOStream *stream, const aiQuatKey &v) {
const size_t t = Write<double>(stream, v.mTime);
return t + Write<aiQuaternion>(stream, v.mValue);
}
template <typename T>
inline size_t WriteBounds(IOStream *stream, const T *in, unsigned int size) {
T minc, maxc;
ArrayBounds(in, size, minc, maxc);
const size_t t = Write<T>(stream, minc);
return t + Write<T>(stream, maxc);
}
// We use this to write out non-byte arrays so that we write using the specializations.
// This way we avoid writing out extra bytes that potentially come from struct alignment.
template <typename T>
inline size_t WriteArray(IOStream *stream, const T *in, unsigned int size) {
size_t n = 0;
for (unsigned int i = 0; i < size; i++)
n += Write<T>(stream, in[i]);
return n;
}
// ----------------------------------------------------------------------------------
/** @class AssbinChunkWriter
* @brief Chunk writer mechanism for the .assbin file structure
*
* This is a standard in-memory IOStream (most of the code is based on BlobIOStream),
* the difference being that this takes another IOStream as a "container" in the
* constructor, and when it is destroyed, it appends the magic number, the chunk size,
* and the chunk contents to the container stream. This allows relatively easy chunk
* chunk construction, even recursively.
*/
class AssbinChunkWriter final : public IOStream {
public:
AssbinChunkWriter(IOStream *container, uint32_t magic, size_t initial = 4096) :
buffer(nullptr),
magic(magic),
container(container),
cur_size(0),
cursor(0),
initial(initial) {
// empty
}
~AssbinChunkWriter() override {
if (container) {
container->Write(&magic, sizeof(uint32_t), 1);
container->Write(&cursor, sizeof(uint32_t), 1);
container->Write(buffer, 1, cursor);
}
if (buffer) delete[] buffer;
}
void *GetBufferPointer() { return buffer; }
size_t Read(void * /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override {
return 0;
}
aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) override {
return aiReturn_FAILURE;
}
size_t Tell() const override {
return cursor;
}
void Flush() override {
// not implemented
}
size_t FileSize() const override {
return cursor;
}
size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override {
pSize *= pCount;
if (cursor + pSize > cur_size) {
Grow(cursor + pSize);
}
memcpy(buffer + cursor, pvBuffer, pSize);
cursor += pSize;
return pCount;
}
private:
// -------------------------------------------------------------------
void Grow(size_t need = 0) {
size_t new_size = std::max(initial, std::max(need, cur_size + (cur_size >> 1)));
const uint8_t *const old = buffer;
buffer = new uint8_t[new_size];
if (old) {
memcpy(buffer, old, cur_size);
delete[] old;
}
cur_size = new_size;
}
private:
uint8_t *buffer;
uint32_t magic;
IOStream *container;
size_t cur_size, cursor, initial;
};
// ----------------------------------------------------------------------------------
/** @class AssbinFileWriter
* @brief Assbin file writer class
*
* This class writes an .assbin file, and is responsible for the file layout.
*/
class AssbinFileWriter {
private:
bool shortened;
bool compressed;
protected:
// -----------------------------------------------------------------------------------
void WriteBinaryNode(IOStream *container, const aiNode *node) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AINODE);
unsigned int nb_metadata = (node->mMetaData != nullptr ? node->mMetaData->mNumProperties : 0);
Write<aiString>(&chunk, node->mName);
Write<aiMatrix4x4>(&chunk, node->mTransformation);
Write<unsigned int>(&chunk, node->mNumChildren);
Write<unsigned int>(&chunk, node->mNumMeshes);
Write<unsigned int>(&chunk, nb_metadata);
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
Write<unsigned int>(&chunk, node->mMeshes[i]);
}
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
WriteBinaryNode(&chunk, node->mChildren[i]);
}
for (unsigned int i = 0; i < nb_metadata; ++i) {
const aiString &key = node->mMetaData->mKeys[i];
aiMetadataType type = node->mMetaData->mValues[i].mType;
void *value = node->mMetaData->mValues[i].mData;
Write<aiString>(&chunk, key);
Write<uint16_t>(&chunk, (uint16_t)type);
switch (type) {
case AI_BOOL:
Write<bool>(&chunk, *((bool *)value));
break;
case AI_INT32:
Write<int32_t>(&chunk, *((int32_t *)value));
break;
case AI_UINT64:
Write<uint64_t>(&chunk, *((uint64_t *)value));
break;
case AI_FLOAT:
Write<float>(&chunk, *((float *)value));
break;
case AI_DOUBLE:
Write<double>(&chunk, *((double *)value));
break;
case AI_AISTRING:
Write<aiString>(&chunk, *((aiString *)value));
break;
case AI_AIVECTOR3D:
Write<aiVector3D>(&chunk, *((aiVector3D *)value));
break;
#ifdef SWIG
case FORCE_32BIT:
#endif // SWIG
default:
break;
}
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryTexture(IOStream *container, const aiTexture *tex) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AITEXTURE);
Write<unsigned int>(&chunk, tex->mWidth);
Write<unsigned int>(&chunk, tex->mHeight);
// Write the texture format, but don't include the null terminator.
chunk.Write(tex->achFormatHint, sizeof(char), HINTMAXTEXTURELEN - 1);
if (!shortened) {
if (!tex->mHeight) {
chunk.Write(tex->pcData, 1, tex->mWidth);
} else {
chunk.Write(tex->pcData, 1, tex->mWidth * tex->mHeight * 4);
}
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryBone(IOStream *container, const aiBone *b) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIBONE);
Write<aiString>(&chunk, b->mName);
Write<unsigned int>(&chunk, b->mNumWeights);
Write<aiMatrix4x4>(&chunk, b->mOffsetMatrix);
// for the moment we write dumb min/max values for the bones, too.
// maybe I'll add a better, hash-like solution later
if (shortened) {
WriteBounds(&chunk, b->mWeights, b->mNumWeights);
} // else write as usual
else
WriteArray<aiVertexWeight>(&chunk, b->mWeights, b->mNumWeights);
}
// -----------------------------------------------------------------------------------
void WriteBinaryMesh(IOStream *container, const aiMesh *mesh) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMESH);
Write<unsigned int>(&chunk, mesh->mPrimitiveTypes);
Write<unsigned int>(&chunk, mesh->mNumVertices);
Write<unsigned int>(&chunk, mesh->mNumFaces);
Write<unsigned int>(&chunk, mesh->mNumBones);
Write<unsigned int>(&chunk, mesh->mMaterialIndex);
// first of all, write bits for all existent vertex components
unsigned int c = 0;
if (mesh->mVertices) {
c |= ASSBIN_MESH_HAS_POSITIONS;
}
if (mesh->mNormals) {
c |= ASSBIN_MESH_HAS_NORMALS;
}
if (mesh->mTangents && mesh->mBitangents) {
c |= ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS;
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) {
if (!mesh->mTextureCoords[n]) {
break;
}
c |= ASSBIN_MESH_HAS_TEXCOORD(n);
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) {
if (!mesh->mColors[n]) {
break;
}
c |= ASSBIN_MESH_HAS_COLOR(n);
}
Write<unsigned int>(&chunk, c);
aiVector3D minVec, maxVec;
if (mesh->mVertices) {
if (shortened) {
WriteBounds(&chunk, mesh->mVertices, mesh->mNumVertices);
} // else write as usual
else
WriteArray<aiVector3D>(&chunk, mesh->mVertices, mesh->mNumVertices);
}
if (mesh->mNormals) {
if (shortened) {
WriteBounds(&chunk, mesh->mNormals, mesh->mNumVertices);
} // else write as usual
else
WriteArray<aiVector3D>(&chunk, mesh->mNormals, mesh->mNumVertices);
}
if (mesh->mTangents && mesh->mBitangents) {
if (shortened) {
WriteBounds(&chunk, mesh->mTangents, mesh->mNumVertices);
WriteBounds(&chunk, mesh->mBitangents, mesh->mNumVertices);
} // else write as usual
else {
WriteArray<aiVector3D>(&chunk, mesh->mTangents, mesh->mNumVertices);
WriteArray<aiVector3D>(&chunk, mesh->mBitangents, mesh->mNumVertices);
}
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) {
if (!mesh->mColors[n])
break;
if (shortened) {
WriteBounds(&chunk, mesh->mColors[n], mesh->mNumVertices);
} // else write as usual
else
WriteArray<aiColor4D>(&chunk, mesh->mColors[n], mesh->mNumVertices);
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) {
if (!mesh->mTextureCoords[n])
break;
// write number of UV components
Write<unsigned int>(&chunk, mesh->mNumUVComponents[n]);
if (shortened) {
WriteBounds(&chunk, mesh->mTextureCoords[n], mesh->mNumVertices);
} // else write as usual
else
WriteArray<aiVector3D>(&chunk, mesh->mTextureCoords[n], mesh->mNumVertices);
}
// write faces. There are no floating-point calculations involved
// in these, so we can write a simple hash over the face data
// to the dump file. We generate a single 32 Bit hash for 512 faces
// using Assimp's standard hashing function.
if (shortened) {
unsigned int processed = 0;
for (unsigned int job; (job = std::min(mesh->mNumFaces - processed, 512u)); processed += job) {
uint32_t hash = 0;
for (unsigned int a = 0; a < job; ++a) {
const aiFace &f = mesh->mFaces[processed + a];
uint32_t tmp = f.mNumIndices;
hash = SuperFastHash(reinterpret_cast<const char *>(&tmp), sizeof tmp, hash);
for (unsigned int i = 0; i < f.mNumIndices; ++i) {
static_assert(AI_MAX_VERTICES <= 0xffffffff, "AI_MAX_VERTICES <= 0xffffffff");
tmp = static_cast<uint32_t>(f.mIndices[i]);
hash = SuperFastHash(reinterpret_cast<const char *>(&tmp), sizeof tmp, hash);
}
}
Write<unsigned int>(&chunk, hash);
}
} else // else write as usual
{
// if there are less than 2^16 vertices, we can simply use 16 bit integers ...
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
const aiFace &f = mesh->mFaces[i];
static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff");
Write<uint16_t>(&chunk, static_cast<uint16_t>(f.mNumIndices));
for (unsigned int a = 0; a < f.mNumIndices; ++a) {
if (mesh->mNumVertices < (1u << 16)) {
Write<uint16_t>(&chunk, static_cast<uint16_t>(f.mIndices[a]));
} else {
Write<unsigned int>(&chunk, f.mIndices[a]);
}
}
}
}
// write bones
if (mesh->mNumBones) {
for (unsigned int a = 0; a < mesh->mNumBones; ++a) {
const aiBone *b = mesh->mBones[a];
WriteBinaryBone(&chunk, b);
}
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryMaterialProperty(IOStream *container, const aiMaterialProperty *prop) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMATERIALPROPERTY);
Write<aiString>(&chunk, prop->mKey);
Write<unsigned int>(&chunk, prop->mSemantic);
Write<unsigned int>(&chunk, prop->mIndex);
Write<unsigned int>(&chunk, prop->mDataLength);
Write<unsigned int>(&chunk, (unsigned int)prop->mType);
chunk.Write(prop->mData, 1, prop->mDataLength);
}
// -----------------------------------------------------------------------------------
void WriteBinaryMaterial(IOStream *container, const aiMaterial *mat) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMATERIAL);
Write<unsigned int>(&chunk, mat->mNumProperties);
for (unsigned int i = 0; i < mat->mNumProperties; ++i) {
WriteBinaryMaterialProperty(&chunk, mat->mProperties[i]);
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryNodeAnim(IOStream *container, const aiNodeAnim *nd) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AINODEANIM);
Write<aiString>(&chunk, nd->mNodeName);
Write<unsigned int>(&chunk, nd->mNumPositionKeys);
Write<unsigned int>(&chunk, nd->mNumRotationKeys);
Write<unsigned int>(&chunk, nd->mNumScalingKeys);
Write<unsigned int>(&chunk, nd->mPreState);
Write<unsigned int>(&chunk, nd->mPostState);
if (nd->mPositionKeys) {
if (shortened) {
WriteBounds(&chunk, nd->mPositionKeys, nd->mNumPositionKeys);
} // else write as usual
else
WriteArray<aiVectorKey>(&chunk, nd->mPositionKeys, nd->mNumPositionKeys);
}
if (nd->mRotationKeys) {
if (shortened) {
WriteBounds(&chunk, nd->mRotationKeys, nd->mNumRotationKeys);
} // else write as usual
else
WriteArray<aiQuatKey>(&chunk, nd->mRotationKeys, nd->mNumRotationKeys);
}
if (nd->mScalingKeys) {
if (shortened) {
WriteBounds(&chunk, nd->mScalingKeys, nd->mNumScalingKeys);
} // else write as usual
else
WriteArray<aiVectorKey>(&chunk, nd->mScalingKeys, nd->mNumScalingKeys);
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryAnim(IOStream *container, const aiAnimation *anim) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIANIMATION);
Write<aiString>(&chunk, anim->mName);
Write<double>(&chunk, anim->mDuration);
Write<double>(&chunk, anim->mTicksPerSecond);
Write<unsigned int>(&chunk, anim->mNumChannels);
for (unsigned int a = 0; a < anim->mNumChannels; ++a) {
const aiNodeAnim *nd = anim->mChannels[a];
WriteBinaryNodeAnim(&chunk, nd);
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryLight(IOStream *container, const aiLight *l) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AILIGHT);
Write<aiString>(&chunk, l->mName);
Write<unsigned int>(&chunk, l->mType);
Write<aiVector3D>(&chunk, l->mPosition);
Write<aiVector3D>(&chunk, l->mDirection);
Write<aiVector3D>(&chunk, l->mUp);
if (l->mType != aiLightSource_DIRECTIONAL) {
Write<float>(&chunk, l->mAttenuationConstant);
Write<float>(&chunk, l->mAttenuationLinear);
Write<float>(&chunk, l->mAttenuationQuadratic);
}
Write<aiColor3D>(&chunk, l->mColorDiffuse);
Write<aiColor3D>(&chunk, l->mColorSpecular);
Write<aiColor3D>(&chunk, l->mColorAmbient);
if (l->mType == aiLightSource_SPOT) {
Write<float>(&chunk, l->mAngleInnerCone);
Write<float>(&chunk, l->mAngleOuterCone);
}
}
// -----------------------------------------------------------------------------------
void WriteBinaryCamera(IOStream *container, const aiCamera *cam) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AICAMERA);
Write<aiString>(&chunk, cam->mName);
Write<aiVector3D>(&chunk, cam->mPosition);
Write<aiVector3D>(&chunk, cam->mLookAt);
Write<aiVector3D>(&chunk, cam->mUp);
Write<float>(&chunk, cam->mHorizontalFOV);
Write<float>(&chunk, cam->mClipPlaneNear);
Write<float>(&chunk, cam->mClipPlaneFar);
Write<float>(&chunk, cam->mAspect);
}
// -----------------------------------------------------------------------------------
void WriteBinaryScene(IOStream *container, const aiScene *scene) {
AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AISCENE);
// basic scene information
Write<unsigned int>(&chunk, scene->mFlags);
Write<unsigned int>(&chunk, scene->mNumMeshes);
Write<unsigned int>(&chunk, scene->mNumMaterials);
Write<unsigned int>(&chunk, scene->mNumAnimations);
Write<unsigned int>(&chunk, scene->mNumTextures);
Write<unsigned int>(&chunk, scene->mNumLights);
Write<unsigned int>(&chunk, scene->mNumCameras);
// write node graph
WriteBinaryNode(&chunk, scene->mRootNode);
// write all meshes
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
const aiMesh *mesh = scene->mMeshes[i];
WriteBinaryMesh(&chunk, mesh);
}
// write materials
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
const aiMaterial *mat = scene->mMaterials[i];
WriteBinaryMaterial(&chunk, mat);
}
// write all animations
for (unsigned int i = 0; i < scene->mNumAnimations; ++i) {
const aiAnimation *anim = scene->mAnimations[i];
WriteBinaryAnim(&chunk, anim);
}
// write all textures
for (unsigned int i = 0; i < scene->mNumTextures; ++i) {
const aiTexture *mesh = scene->mTextures[i];
WriteBinaryTexture(&chunk, mesh);
}
// write lights
for (unsigned int i = 0; i < scene->mNumLights; ++i) {
const aiLight *l = scene->mLights[i];
WriteBinaryLight(&chunk, l);
}
// write cameras
for (unsigned int i = 0; i < scene->mNumCameras; ++i) {
const aiCamera *cam = scene->mCameras[i];
WriteBinaryCamera(&chunk, cam);
}
}
public:
AssbinFileWriter(bool shortened, bool compressed) :
shortened(shortened), compressed(compressed) {
}
// -----------------------------------------------------------------------------------
// Write a binary model dump
void WriteBinaryDump(const char *pFile, const char *cmd, IOSystem *pIOSystem, const aiScene *pScene) {
IOStream *out = pIOSystem->Open(pFile, "wb");
if (!out)
throw std::runtime_error("Unable to open output file " + std::string(pFile) + '\n');
auto CloseIOStream = [&]() {
if (out) {
pIOSystem->Close(out);
out = nullptr; // Ensure this is only done once.
}
};
try {
time_t tt = time(nullptr);
#if _WIN32
tm *p = gmtime(&tt);
#else
struct tm now;
tm *p = gmtime_r(&tt, &now);
#endif
// header
char s[64];
memset(s, 0, 64);
#if _MSC_VER >= 1400
sprintf_s(s, "ASSIMP.binary-dump.%s", asctime(p));
#else
ai_snprintf(s, 64, "ASSIMP.binary-dump.%s", asctime(p));
#endif
out->Write(s, 44, 1);
// == 44 bytes
Write<unsigned int>(out, ASSBIN_VERSION_MAJOR);
Write<unsigned int>(out, ASSBIN_VERSION_MINOR);
Write<unsigned int>(out, aiGetVersionRevision());
Write<unsigned int>(out, aiGetCompileFlags());
Write<uint16_t>(out, shortened);
Write<uint16_t>(out, compressed);
// == 20 bytes
char buff[256] = { 0 };
ai_snprintf(buff, 256, "%s", pFile);
out->Write(buff, sizeof(char), 256);
memset(buff, 0, sizeof(buff));
ai_snprintf(buff, 128, "%s", cmd);
out->Write(buff, sizeof(char), 128);
// leave 64 bytes free for future extensions
memset(buff, 0xcd, 64);
out->Write(buff, sizeof(char), 64);
// == 435 bytes
// ==== total header size: 512 bytes
ai_assert(out->Tell() == ASSBIN_HEADER_LENGTH);
// Up to here the data is uncompressed. For compressed files, the rest
// is compressed using standard DEFLATE from zlib.
if (compressed) {
AssbinChunkWriter uncompressedStream(nullptr, 0);
WriteBinaryScene(&uncompressedStream, pScene);
uLongf uncompressedSize = static_cast<uLongf>(uncompressedStream.Tell());
uLongf compressedSize = (uLongf)compressBound(uncompressedSize);
uint8_t *compressedBuffer = new uint8_t[compressedSize];
int res = compress2(compressedBuffer, &compressedSize, (const Bytef *)uncompressedStream.GetBufferPointer(), uncompressedSize, 9);
if (res != Z_OK) {
delete[] compressedBuffer;
throw DeadlyExportError("Compression failed.");
}
out->Write(&uncompressedSize, sizeof(uint32_t), 1);
out->Write(compressedBuffer, sizeof(char), compressedSize);
delete[] compressedBuffer;
} else {
WriteBinaryScene(out, pScene);
}
CloseIOStream();
} catch (...) {
CloseIOStream();
throw;
}
}
};
void DumpSceneToAssbin(
const char *pFile, const char *cmd, IOSystem *pIOSystem,
const aiScene *pScene, bool shortened, bool compressed) {
AssbinFileWriter fileWriter(shortened, compressed);
fileWriter.WriteBinaryDump(pFile, cmd, pIOSystem, pScene);
}
#if _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
} // end of namespace Assimp

View File

@@ -0,0 +1,65 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssbinFileWriter.h
* @brief Declaration of Assbin file writer.
*/
#ifndef AI_ASSBINFILEWRITER_H_INC
#define AI_ASSBINFILEWRITER_H_INC
#include <assimp/defs.h>
#include <assimp/scene.h>
#include <assimp/IOSystem.hpp>
namespace Assimp {
void ASSIMP_API DumpSceneToAssbin(
const char *pFile,
const char *cmd,
IOSystem *pIOSystem,
const aiScene *pScene,
bool shortened,
bool compressed);
}
#endif // AI_ASSBINFILEWRITER_H_INC

View File

@@ -0,0 +1,741 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/** @file AssbinLoader.cpp
* @brief Implementation of the .assbin importer class
*
* see assbin_chunks.h
*/
#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER
// internal headers
#include "AssbinLoader.h"
#include "Common/assbin_chunks.h"
#include <assimp/MemoryIOWrapper.h>
#include <assimp/anim.h>
#include <assimp/importerdesc.h>
#include <assimp/mesh.h>
#include <assimp/scene.h>
#include <memory>
#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
#include <zlib.h>
#else
#include <contrib/zlib/zlib.h>
#endif
using namespace Assimp;
static constexpr aiImporterDesc desc = {
"Assimp Binary Importer",
"Gargaj / Conspiracy",
"",
"",
aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour,
0,
0,
0,
0,
"assbin"
};
// -----------------------------------------------------------------------------------
const aiImporterDesc *AssbinImporter::GetInfo() const {
return &desc;
}
// -----------------------------------------------------------------------------------
bool AssbinImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
IOStream *in = pIOHandler->Open(pFile);
if (nullptr == in) {
return false;
}
char s[32];
const size_t read = in->Read(s, sizeof(char), 32);
pIOHandler->Close(in);
if (read < 19) {
return false;
}
return strncmp(s, "ASSIMP.binary-dump.", 19) == 0;
}
// -----------------------------------------------------------------------------------
template <typename T>
T Read(IOStream *stream) {
T t;
size_t res = stream->Read(&t, sizeof(T), 1);
if (res != 1) {
throw DeadlyImportError("Unexpected EOF");
}
return t;
}
// -----------------------------------------------------------------------------------
template <>
aiVector3D Read<aiVector3D>(IOStream *stream) {
aiVector3D v;
v.x = Read<ai_real>(stream);
v.y = Read<ai_real>(stream);
v.z = Read<ai_real>(stream);
return v;
}
// -----------------------------------------------------------------------------------
template <>
aiColor4D Read<aiColor4D>(IOStream *stream) {
aiColor4D c;
c.r = Read<ai_real>(stream);
c.g = Read<ai_real>(stream);
c.b = Read<ai_real>(stream);
c.a = Read<ai_real>(stream);
return c;
}
// -----------------------------------------------------------------------------------
template <>
aiQuaternion Read<aiQuaternion>(IOStream *stream) {
aiQuaternion v;
v.w = Read<ai_real>(stream);
v.x = Read<ai_real>(stream);
v.y = Read<ai_real>(stream);
v.z = Read<ai_real>(stream);
return v;
}
// -----------------------------------------------------------------------------------
template <>
aiString Read<aiString>(IOStream *stream) {
aiString s;
stream->Read(&s.length, 4, 1);
if (s.length) {
stream->Read(s.data, s.length, 1);
}
s.data[s.length] = 0;
return s;
}
// -----------------------------------------------------------------------------------
template <>
aiVertexWeight Read<aiVertexWeight>(IOStream *stream) {
aiVertexWeight w;
w.mVertexId = Read<unsigned int>(stream);
w.mWeight = Read<ai_real>(stream);
return w;
}
// -----------------------------------------------------------------------------------
template <>
aiMatrix4x4 Read<aiMatrix4x4>(IOStream *stream) {
aiMatrix4x4 m;
for (unsigned int i = 0; i < 4; ++i) {
for (unsigned int i2 = 0; i2 < 4; ++i2) {
m[i][i2] = Read<ai_real>(stream);
}
}
return m;
}
// -----------------------------------------------------------------------------------
template <>
aiVectorKey Read<aiVectorKey>(IOStream *stream) {
aiVectorKey v;
v.mTime = Read<double>(stream);
v.mValue = Read<aiVector3D>(stream);
return v;
}
// -----------------------------------------------------------------------------------
template <>
aiQuatKey Read<aiQuatKey>(IOStream *stream) {
aiQuatKey v;
v.mTime = Read<double>(stream);
v.mValue = Read<aiQuaternion>(stream);
return v;
}
// -----------------------------------------------------------------------------------
template <typename T>
void ReadArray(IOStream *stream, T *out, unsigned int size) {
ai_assert(nullptr != stream);
ai_assert(nullptr != out);
for (unsigned int i = 0; i < size; i++) {
out[i] = Read<T>(stream);
}
}
// -----------------------------------------------------------------------------------
template <typename T>
void ReadBounds(IOStream *stream, T * /*p*/, unsigned int n) {
// not sure what to do here, the data isn't really useful.
stream->Seek(sizeof(T) * n, aiOrigin_CUR);
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryNode(IOStream *stream, aiNode **onode, aiNode *parent) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODE)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
std::unique_ptr<aiNode> node(new aiNode());
node->mName = Read<aiString>(stream);
node->mTransformation = Read<aiMatrix4x4>(stream);
unsigned numChildren = Read<unsigned int>(stream);
unsigned numMeshes = Read<unsigned int>(stream);
unsigned int nb_metadata = Read<unsigned int>(stream);
if (parent) {
node->mParent = parent;
}
if (numMeshes) {
node->mMeshes = new unsigned int[numMeshes];
for (unsigned int i = 0; i < numMeshes; ++i) {
node->mMeshes[i] = Read<unsigned int>(stream);
node->mNumMeshes++;
}
}
if (numChildren) {
node->mChildren = new aiNode *[numChildren];
for (unsigned int i = 0; i < numChildren; ++i) {
ReadBinaryNode(stream, &node->mChildren[i], node.get());
node->mNumChildren++;
}
}
if (nb_metadata > 0) {
node->mMetaData = aiMetadata::Alloc(nb_metadata);
for (unsigned int i = 0; i < nb_metadata; ++i) {
node->mMetaData->mKeys[i] = Read<aiString>(stream);
node->mMetaData->mValues[i].mType = (aiMetadataType)Read<uint16_t>(stream);
void *data = nullptr;
switch (node->mMetaData->mValues[i].mType) {
case AI_BOOL:
data = new bool(Read<bool>(stream));
break;
case AI_INT32:
data = new int32_t(Read<int32_t>(stream));
break;
case AI_UINT64:
data = new uint64_t(Read<uint64_t>(stream));
break;
case AI_FLOAT:
data = new ai_real(Read<ai_real>(stream));
break;
case AI_DOUBLE:
data = new double(Read<double>(stream));
break;
case AI_AISTRING:
data = new aiString(Read<aiString>(stream));
break;
case AI_AIVECTOR3D:
data = new aiVector3D(Read<aiVector3D>(stream));
break;
#ifndef SWIG
case FORCE_32BIT:
#endif // SWIG
default:
break;
}
node->mMetaData->mValues[i].mData = data;
}
}
*onode = node.release();
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryBone(IOStream *stream, aiBone *b) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIBONE)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
b->mName = Read<aiString>(stream);
b->mNumWeights = Read<unsigned int>(stream);
b->mOffsetMatrix = Read<aiMatrix4x4>(stream);
// for the moment we write dumb min/max values for the bones, too.
// maybe I'll add a better, hash-like solution later
if (shortened) {
ReadBounds(stream, b->mWeights, b->mNumWeights);
} else {
// else write as usual
b->mWeights = new aiVertexWeight[b->mNumWeights];
ReadArray<aiVertexWeight>(stream, b->mWeights, b->mNumWeights);
}
}
// -----------------------------------------------------------------------------------
static bool fitsIntoUI16(unsigned int mNumVertices) {
return (mNumVertices < (1u << 16));
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryMesh(IOStream *stream, aiMesh *mesh) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMESH)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
mesh->mPrimitiveTypes = Read<unsigned int>(stream);
mesh->mNumVertices = Read<unsigned int>(stream);
mesh->mNumFaces = Read<unsigned int>(stream);
mesh->mNumBones = Read<unsigned int>(stream);
mesh->mMaterialIndex = Read<unsigned int>(stream);
// first of all, write bits for all existent vertex components
unsigned int c = Read<unsigned int>(stream);
if (c & ASSBIN_MESH_HAS_POSITIONS) {
if (shortened) {
ReadBounds(stream, mesh->mVertices, mesh->mNumVertices);
} else {
// else write as usual
mesh->mVertices = new aiVector3D[mesh->mNumVertices];
ReadArray<aiVector3D>(stream, mesh->mVertices, mesh->mNumVertices);
}
}
if (c & ASSBIN_MESH_HAS_NORMALS) {
if (shortened) {
ReadBounds(stream, mesh->mNormals, mesh->mNumVertices);
} else {
// else write as usual
mesh->mNormals = new aiVector3D[mesh->mNumVertices];
ReadArray<aiVector3D>(stream, mesh->mNormals, mesh->mNumVertices);
}
}
if (c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) {
if (shortened) {
ReadBounds(stream, mesh->mTangents, mesh->mNumVertices);
ReadBounds(stream, mesh->mBitangents, mesh->mNumVertices);
} else {
// else write as usual
mesh->mTangents = new aiVector3D[mesh->mNumVertices];
ReadArray<aiVector3D>(stream, mesh->mTangents, mesh->mNumVertices);
mesh->mBitangents = new aiVector3D[mesh->mNumVertices];
ReadArray<aiVector3D>(stream, mesh->mBitangents, mesh->mNumVertices);
}
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) {
if (!(c & ASSBIN_MESH_HAS_COLOR(n))) {
break;
}
if (shortened) {
ReadBounds(stream, mesh->mColors[n], mesh->mNumVertices);
} else {
// else write as usual
mesh->mColors[n] = new aiColor4D[mesh->mNumVertices];
ReadArray<aiColor4D>(stream, mesh->mColors[n], mesh->mNumVertices);
}
}
for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) {
if (!(c & ASSBIN_MESH_HAS_TEXCOORD(n))) {
break;
}
// write number of UV components
mesh->mNumUVComponents[n] = Read<unsigned int>(stream);
if (shortened) {
ReadBounds(stream, mesh->mTextureCoords[n], mesh->mNumVertices);
} else {
// else write as usual
mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
ReadArray<aiVector3D>(stream, mesh->mTextureCoords[n], mesh->mNumVertices);
}
}
// write faces. There are no floating-point calculations involved
// in these, so we can write a simple hash over the face data
// to the dump file. We generate a single 32 Bit hash for 512 faces
// using Assimp's standard hashing function.
if (shortened) {
Read<unsigned int>(stream);
} else {
// else write as usual
// if there are less than 2^16 vertices, we can simply use 16 bit integers ...
mesh->mFaces = new aiFace[mesh->mNumFaces];
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
aiFace &f = mesh->mFaces[i];
static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff");
f.mNumIndices = Read<uint16_t>(stream);
f.mIndices = new unsigned int[f.mNumIndices];
for (unsigned int a = 0; a < f.mNumIndices; ++a) {
// Check if unsigned short ( 16 bit ) are big enough for the indices
if (fitsIntoUI16(mesh->mNumVertices)) {
f.mIndices[a] = Read<uint16_t>(stream);
} else {
f.mIndices[a] = Read<unsigned int>(stream);
}
}
}
}
// write bones
if (mesh->mNumBones) {
mesh->mBones = new C_STRUCT aiBone *[mesh->mNumBones];
for (unsigned int a = 0; a < mesh->mNumBones; ++a) {
mesh->mBones[a] = new aiBone();
ReadBinaryBone(stream, mesh->mBones[a]);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryMaterialProperty(IOStream *stream, aiMaterialProperty *prop) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIALPROPERTY)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
prop->mKey = Read<aiString>(stream);
prop->mSemantic = Read<unsigned int>(stream);
prop->mIndex = Read<unsigned int>(stream);
prop->mDataLength = Read<unsigned int>(stream);
prop->mType = (aiPropertyTypeInfo)Read<unsigned int>(stream);
prop->mData = new char[prop->mDataLength];
stream->Read(prop->mData, 1, prop->mDataLength);
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryMaterial(IOStream *stream, aiMaterial *mat) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIAL)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
mat->mNumAllocated = mat->mNumProperties = Read<unsigned int>(stream);
if (mat->mNumProperties) {
if (mat->mProperties) {
delete[] mat->mProperties;
}
mat->mProperties = new aiMaterialProperty *[mat->mNumProperties];
for (unsigned int i = 0; i < mat->mNumProperties; ++i) {
mat->mProperties[i] = new aiMaterialProperty();
ReadBinaryMaterialProperty(stream, mat->mProperties[i]);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryNodeAnim(IOStream *stream, aiNodeAnim *nd) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODEANIM)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
nd->mNodeName = Read<aiString>(stream);
nd->mNumPositionKeys = Read<unsigned int>(stream);
nd->mNumRotationKeys = Read<unsigned int>(stream);
nd->mNumScalingKeys = Read<unsigned int>(stream);
nd->mPreState = (aiAnimBehaviour)Read<unsigned int>(stream);
nd->mPostState = (aiAnimBehaviour)Read<unsigned int>(stream);
if (nd->mNumPositionKeys) {
if (shortened) {
ReadBounds(stream, nd->mPositionKeys, nd->mNumPositionKeys);
} // else write as usual
else {
nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
ReadArray<aiVectorKey>(stream, nd->mPositionKeys, nd->mNumPositionKeys);
}
}
if (nd->mNumRotationKeys) {
if (shortened) {
ReadBounds(stream, nd->mRotationKeys, nd->mNumRotationKeys);
} else {
// else write as usual
nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys];
ReadArray<aiQuatKey>(stream, nd->mRotationKeys, nd->mNumRotationKeys);
}
}
if (nd->mNumScalingKeys) {
if (shortened) {
ReadBounds(stream, nd->mScalingKeys, nd->mNumScalingKeys);
} else {
// else write as usual
nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys];
ReadArray<aiVectorKey>(stream, nd->mScalingKeys, nd->mNumScalingKeys);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryAnim(IOStream *stream, aiAnimation *anim) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIANIMATION)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
anim->mName = Read<aiString>(stream);
anim->mDuration = Read<double>(stream);
anim->mTicksPerSecond = Read<double>(stream);
anim->mNumChannels = Read<unsigned int>(stream);
if (anim->mNumChannels) {
anim->mChannels = new aiNodeAnim *[anim->mNumChannels];
for (unsigned int a = 0; a < anim->mNumChannels; ++a) {
anim->mChannels[a] = new aiNodeAnim();
ReadBinaryNodeAnim(stream, anim->mChannels[a]);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryTexture(IOStream *stream, aiTexture *tex) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AITEXTURE)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
tex->mWidth = Read<unsigned int>(stream);
tex->mHeight = Read<unsigned int>(stream);
stream->Read(tex->achFormatHint, sizeof(char), HINTMAXTEXTURELEN - 1);
if (!shortened) {
if (!tex->mHeight) {
tex->pcData = new aiTexel[tex->mWidth];
stream->Read(tex->pcData, 1, tex->mWidth);
} else {
tex->pcData = new aiTexel[tex->mWidth * tex->mHeight];
stream->Read(tex->pcData, 1, tex->mWidth * tex->mHeight * 4);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryLight(IOStream *stream, aiLight *l) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AILIGHT)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
l->mName = Read<aiString>(stream);
l->mType = (aiLightSourceType)Read<unsigned int>(stream);
l->mPosition = Read<aiVector3D>(stream);
l->mDirection = Read<aiVector3D>(stream);
l->mUp = Read<aiVector3D>(stream);
if (l->mType != aiLightSource_DIRECTIONAL) {
l->mAttenuationConstant = Read<float>(stream);
l->mAttenuationLinear = Read<float>(stream);
l->mAttenuationQuadratic = Read<float>(stream);
}
l->mColorDiffuse = Read<aiColor3D>(stream);
l->mColorSpecular = Read<aiColor3D>(stream);
l->mColorAmbient = Read<aiColor3D>(stream);
if (l->mType == aiLightSource_SPOT) {
l->mAngleInnerCone = Read<float>(stream);
l->mAngleOuterCone = Read<float>(stream);
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryCamera(IOStream *stream, aiCamera *cam) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AICAMERA)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
cam->mName = Read<aiString>(stream);
cam->mPosition = Read<aiVector3D>(stream);
cam->mLookAt = Read<aiVector3D>(stream);
cam->mUp = Read<aiVector3D>(stream);
cam->mHorizontalFOV = Read<float>(stream);
cam->mClipPlaneNear = Read<float>(stream);
cam->mClipPlaneFar = Read<float>(stream);
cam->mAspect = Read<float>(stream);
}
// -----------------------------------------------------------------------------------
void AssbinImporter::ReadBinaryScene(IOStream *stream, aiScene *scene) {
if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AISCENE)
throw DeadlyImportError("Magic chunk identifiers are wrong!");
/*uint32_t size =*/Read<uint32_t>(stream);
scene->mFlags = Read<unsigned int>(stream);
scene->mNumMeshes = Read<unsigned int>(stream);
scene->mNumMaterials = Read<unsigned int>(stream);
scene->mNumAnimations = Read<unsigned int>(stream);
scene->mNumTextures = Read<unsigned int>(stream);
scene->mNumLights = Read<unsigned int>(stream);
scene->mNumCameras = Read<unsigned int>(stream);
// Read node graph
//scene->mRootNode = new aiNode[1];
ReadBinaryNode(stream, &scene->mRootNode, (aiNode *)nullptr);
// Read all meshes
if (scene->mNumMeshes) {
scene->mMeshes = new aiMesh *[scene->mNumMeshes];
memset(scene->mMeshes, 0, scene->mNumMeshes * sizeof(aiMesh *));
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
scene->mMeshes[i] = new aiMesh();
ReadBinaryMesh(stream, scene->mMeshes[i]);
}
}
// Read materials
if (scene->mNumMaterials) {
scene->mMaterials = new aiMaterial *[scene->mNumMaterials];
memset(scene->mMaterials, 0, scene->mNumMaterials * sizeof(aiMaterial *));
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
scene->mMaterials[i] = new aiMaterial();
ReadBinaryMaterial(stream, scene->mMaterials[i]);
}
}
// Read all animations
if (scene->mNumAnimations) {
scene->mAnimations = new aiAnimation *[scene->mNumAnimations];
memset(scene->mAnimations, 0, scene->mNumAnimations * sizeof(aiAnimation *));
for (unsigned int i = 0; i < scene->mNumAnimations; ++i) {
scene->mAnimations[i] = new aiAnimation();
ReadBinaryAnim(stream, scene->mAnimations[i]);
}
}
// Read all textures
if (scene->mNumTextures) {
scene->mTextures = new aiTexture *[scene->mNumTextures];
memset(scene->mTextures, 0, scene->mNumTextures * sizeof(aiTexture *));
for (unsigned int i = 0; i < scene->mNumTextures; ++i) {
scene->mTextures[i] = new aiTexture();
ReadBinaryTexture(stream, scene->mTextures[i]);
}
}
// Read lights
if (scene->mNumLights) {
scene->mLights = new aiLight *[scene->mNumLights];
memset(scene->mLights, 0, scene->mNumLights * sizeof(aiLight *));
for (unsigned int i = 0; i < scene->mNumLights; ++i) {
scene->mLights[i] = new aiLight();
ReadBinaryLight(stream, scene->mLights[i]);
}
}
// Read cameras
if (scene->mNumCameras) {
scene->mCameras = new aiCamera *[scene->mNumCameras];
memset(scene->mCameras, 0, scene->mNumCameras * sizeof(aiCamera *));
for (unsigned int i = 0; i < scene->mNumCameras; ++i) {
scene->mCameras[i] = new aiCamera();
ReadBinaryCamera(stream, scene->mCameras[i]);
}
}
}
// -----------------------------------------------------------------------------------
void AssbinImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
IOStream *stream = pIOHandler->Open(pFile, "rb");
if (nullptr == stream) {
throw DeadlyImportError("ASSBIN: Could not open ", pFile);
}
// signature
stream->Seek(44, aiOrigin_CUR);
unsigned int versionMajor = Read<unsigned int>(stream);
unsigned int versionMinor = Read<unsigned int>(stream);
if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) {
pIOHandler->Close(stream);
throw DeadlyImportError("Invalid version, data format not compatible!");
}
/*unsigned int versionRevision =*/Read<unsigned int>(stream);
/*unsigned int compileFlags =*/Read<unsigned int>(stream);
shortened = Read<uint16_t>(stream) > 0;
compressed = Read<uint16_t>(stream) > 0;
if (shortened) {
pIOHandler->Close(stream);
throw DeadlyImportError("Shortened binaries are not supported!");
}
stream->Seek(256, aiOrigin_CUR); // original filename
stream->Seek(128, aiOrigin_CUR); // options
stream->Seek(64, aiOrigin_CUR); // padding
if (compressed) {
uLongf uncompressedSize = Read<uint32_t>(stream);
uLongf compressedSize = static_cast<uLongf>(stream->FileSize() - stream->Tell());
unsigned char *compressedData = new unsigned char[compressedSize];
size_t len = stream->Read(compressedData, 1, compressedSize);
ai_assert(len == compressedSize);
unsigned char *uncompressedData = new unsigned char[uncompressedSize];
int res = uncompress(uncompressedData, &uncompressedSize, compressedData, (uLong)len);
if (res != Z_OK) {
delete[] uncompressedData;
delete[] compressedData;
pIOHandler->Close(stream);
throw DeadlyImportError("Zlib decompression failed.");
}
MemoryIOStream io(uncompressedData, uncompressedSize);
ReadBinaryScene(&io, pScene);
delete[] uncompressedData;
delete[] compressedData;
} else {
ReadBinaryScene(stream, pScene);
}
pIOHandler->Close(stream);
}
#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER

View File

@@ -0,0 +1,98 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssbinLoader.h
* @brief .assbin File format loader
*/
#ifndef AI_ASSBINIMPORTER_H_INC
#define AI_ASSBINIMPORTER_H_INC
#include <assimp/BaseImporter.h>
struct aiMesh;
struct aiNode;
struct aiBone;
struct aiMaterial;
struct aiMaterialProperty;
struct aiNodeAnim;
struct aiAnimation;
struct aiTexture;
struct aiLight;
struct aiCamera;
#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER
namespace Assimp {
// ---------------------------------------------------------------------------------
/** Importer class for 3D Studio r3 and r4 3DS files
*/
class AssbinImporter : public BaseImporter
{
private:
bool shortened;
bool compressed;
public:
bool CanRead(const std::string& pFile,
IOSystem* pIOHandler, bool checkSig) const override;
const aiImporterDesc* GetInfo() const override;
void InternReadFile(
const std::string& pFile,aiScene* pScene,IOSystem* pIOHandler) override;
void ReadHeader();
void ReadBinaryScene( IOStream * stream, aiScene* pScene );
void ReadBinaryNode( IOStream * stream, aiNode** mRootNode, aiNode* parent );
void ReadBinaryMesh( IOStream * stream, aiMesh* mesh );
void ReadBinaryBone( IOStream * stream, aiBone* bone );
void ReadBinaryMaterial(IOStream * stream, aiMaterial* mat);
void ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop);
void ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd);
void ReadBinaryAnim( IOStream * stream, aiAnimation* anim );
void ReadBinaryTexture(IOStream * stream, aiTexture* tex);
void ReadBinaryLight( IOStream * stream, aiLight* l );
void ReadBinaryCamera( IOStream * stream, aiCamera* cam );
};
} // end of namespace Assimp
#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER
#endif // AI_ASSBINIMPORTER_H_INC

View File

@@ -0,0 +1,117 @@
/*
cencoder.c - c source to a base64 encoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cencode.h" // changed from <B64/cencode.h>
static const int CHARS_PER_LINE = 72;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#endif // _MSC_VER
void base64_init_encodestate(base64_encodestate* state_in)
{
state_in->step = step_A;
state_in->result = 0;
state_in->stepcount = 0;
}
char base64_encode_value(char value_in)
{
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (value_in > 63) return '=';
return encoding[(int)value_in];
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step)
{
while (1)
{
case step_A:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_A;
return (int)(codechar - code_out);
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
case step_B:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_B;
return (int)(codechar - code_out);
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
case step_C:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_C;
return (int)(codechar - code_out);
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
++(state_in->stepcount);
if (state_in->stepcount == CHARS_PER_LINE/4)
{
*codechar++ = '\n';
state_in->stepcount = 0;
}
}
}
/* control should not reach here */
return (int)(codechar - code_out);
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
char* codechar = code_out;
switch (state_in->step)
{
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
*codechar++ = '\n';
return (int)(codechar - code_out);
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER

View File

@@ -0,0 +1,35 @@
/*
cencode.h - c header for a base64 encoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CENCODE_H
#define BASE64_CENCODE_H
#ifdef _MSC_VER
#pragma warning(disable : 4127 )
#endif // _MSC_VER
typedef enum
{
step_A, step_B, step_C
} base64_encodestep;
typedef struct
{
base64_encodestep step;
char result;
int stepcount;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
#endif /* BASE64_CENCODE_H */

View File

@@ -0,0 +1,823 @@
/*
Assimp2Json
Copyright (c) 2011, Alexander C. Gessler
Licensed under a 3-clause BSD license. See the LICENSE file for more information.
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
#include <assimp/scene.h>
#include <assimp/ai_assert.h>
#include <assimp/Exporter.hpp>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
#include <assimp/Importer.hpp>
#include <assimp/Exceptional.h>
#include <cassert>
#include <limits>
#include <memory>
#include <sstream>
#define CURRENT_FORMAT_VERSION 100
#include "mesh_splitter.h"
extern "C" {
# include "cencode.h"
}
namespace Assimp {
// Forward declarations
void ExportAssimp2Json(const char *, Assimp::IOSystem *, const aiScene *, const Assimp::ExportProperties *);
// small utility class to simplify serializing the aiScene to Json
class JSONWriter {
public:
enum {
Flag_DoNotIndent = 0x1,
Flag_WriteSpecialFloats = 0x2,
Flag_SkipWhitespaces = 0x4
};
JSONWriter(Assimp::IOStream &out, unsigned int flags = 0u) :
out(out), indent (""), newline("\n"), space(" "), buff (), first(false), flags(flags) {
// make sure that all formatting happens using the standard, C locale and not the user's current locale
buff.imbue(std::locale("C"));
if (flags & Flag_SkipWhitespaces) {
newline = "";
space = "";
}
}
~JSONWriter() {
Flush();
}
void Flush() {
const std::string s = buff.str();
out.Write(s.c_str(), s.length(), 1);
buff.clear();
}
void PushIndent() {
indent += '\t';
}
void PopIndent() {
indent.erase(indent.end() - 1);
}
void Key(const std::string &name) {
AddIndentation();
Delimit();
buff << '\"' + name + "\":" << space;
}
template <typename Literal>
void Element(const Literal &name) {
AddIndentation();
Delimit();
LiteralToString(buff, name) << newline;
}
template <typename Literal>
void SimpleValue(const Literal &s) {
LiteralToString(buff, s) << newline;
}
void SimpleValue(const void *buffer, size_t len) {
base64_encodestate s;
base64_init_encodestate(&s);
char *const cur_out = new char[std::max(len * 2, static_cast<size_t>(16u))];
const int n = base64_encode_block(reinterpret_cast<const char *>(buffer), static_cast<int>(len), cur_out, &s);
cur_out[n + base64_encode_blockend(cur_out + n, &s)] = '\0';
// base64 encoding may add newlines, but JSON strings may not contain 'real' newlines
// (only escaped ones). Remove any newlines in out.
for (char *cur = cur_out; *cur; ++cur) {
if (*cur == '\n') {
*cur = ' ';
}
}
buff << '\"' << cur_out << "\"" << newline;
delete[] cur_out;
}
void StartObj(bool is_element = false) {
// if this appears as a plain array element, we need to insert a delimiter and we should also indent it
if (is_element) {
AddIndentation();
if (!first) {
buff << ',';
}
}
first = true;
buff << "{" << newline;
PushIndent();
}
void EndObj() {
PopIndent();
AddIndentation();
first = false;
buff << "}" << newline;
}
void StartArray(bool is_element = false) {
// if this appears as a plain array element, we need to insert a delimiter and we should also indent it
if (is_element) {
AddIndentation();
if (!first) {
buff << ',';
}
}
first = true;
buff << "[" << newline;
PushIndent();
}
void EndArray() {
PopIndent();
AddIndentation();
buff << "]" << newline;
first = false;
}
void AddIndentation() {
if (!(flags & Flag_DoNotIndent) && !(flags & Flag_SkipWhitespaces)) {
buff << indent;
}
}
void Delimit() {
if (!first) {
buff << ',';
} else {
buff << space;
first = false;
}
}
private:
template <typename Literal>
std::stringstream &LiteralToString(std::stringstream &stream, const Literal &s) {
stream << s;
return stream;
}
std::stringstream &LiteralToString(std::stringstream &stream, const aiString &s) {
std::string t;
// escape backslashes and single quotes, both would render the JSON invalid if left as is
t.reserve(s.length);
for (size_t i = 0; i < s.length; ++i) {
if (s.data[i] == '\\' || s.data[i] == '\'' || s.data[i] == '\"') {
t.push_back('\\');
}
t.push_back(s.data[i]);
}
stream << "\"";
stream << t;
stream << "\"";
return stream;
}
std::stringstream &LiteralToString(std::stringstream &stream, float f) {
if (!std::numeric_limits<float>::is_iec559) {
// on a non IEEE-754 platform, we make no assumptions about the representation or existence
// of special floating-point numbers.
stream << f;
return stream;
}
// JSON does not support writing Inf/Nan
// [RFC 4672: "Numeric values that cannot be represented as sequences of digits
// (such as Infinity and NaN) are not permitted."]
// Nevertheless, many parsers will accept the special keywords Infinity, -Infinity and NaN
if (std::numeric_limits<float>::infinity() == fabs(f)) {
if (flags & Flag_WriteSpecialFloats) {
stream << (f < 0 ? "\"-" : "\"") + std::string("Infinity\"");
return stream;
}
// we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr
// std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl;
stream << "0.0";
return stream;
}
// f!=f is the most reliable test for NaNs that I know of
else if (f != f) {
if (flags & Flag_WriteSpecialFloats) {
stream << "\"NaN\"";
return stream;
}
// we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr
// std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl;
stream << "0.0";
return stream;
}
stream << f;
return stream;
}
private:
Assimp::IOStream &out;
std::string indent;
std::string newline;
std::string space;
std::stringstream buff;
bool first;
unsigned int flags;
};
static void Write(JSONWriter &out, const aiVector3D &ai, bool is_elem = true) {
out.StartArray(is_elem);
out.Element(ai.x);
out.Element(ai.y);
out.Element(ai.z);
out.EndArray();
}
static void Write(JSONWriter &out, const aiQuaternion &ai, bool is_elem = true) {
out.StartArray(is_elem);
out.Element(ai.w);
out.Element(ai.x);
out.Element(ai.y);
out.Element(ai.z);
out.EndArray();
}
static void Write(JSONWriter &out, const aiColor3D &ai, bool is_elem = true) {
out.StartArray(is_elem);
out.Element(ai.r);
out.Element(ai.g);
out.Element(ai.b);
out.EndArray();
}
static void Write(JSONWriter &out, const aiMatrix4x4 &ai, bool is_elem = true) {
out.StartArray(is_elem);
for (unsigned int x = 0; x < 4; ++x) {
for (unsigned int y = 0; y < 4; ++y) {
out.Element(ai[x][y]);
}
}
out.EndArray();
}
static void Write(JSONWriter &out, const aiBone &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("offsetmatrix");
Write(out, ai.mOffsetMatrix, false);
out.Key("weights");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumWeights; ++i) {
out.StartArray(true);
out.Element(ai.mWeights[i].mVertexId);
out.Element(ai.mWeights[i].mWeight);
out.EndArray();
}
out.EndArray();
out.EndObj();
}
static void Write(JSONWriter &out, const aiFace &ai, bool is_elem = true) {
out.StartArray(is_elem);
for (unsigned int i = 0; i < ai.mNumIndices; ++i) {
out.Element(ai.mIndices[i]);
}
out.EndArray();
}
static void Write(JSONWriter &out, const aiMesh &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("materialindex");
out.SimpleValue(ai.mMaterialIndex);
out.Key("primitivetypes");
out.SimpleValue(ai.mPrimitiveTypes);
out.Key("vertices");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
out.Element(ai.mVertices[i].x);
out.Element(ai.mVertices[i].y);
out.Element(ai.mVertices[i].z);
}
out.EndArray();
if (ai.HasNormals()) {
out.Key("normals");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
out.Element(ai.mNormals[i].x);
out.Element(ai.mNormals[i].y);
out.Element(ai.mNormals[i].z);
}
out.EndArray();
}
if (ai.HasTangentsAndBitangents()) {
out.Key("tangents");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
out.Element(ai.mTangents[i].x);
out.Element(ai.mTangents[i].y);
out.Element(ai.mTangents[i].z);
}
out.EndArray();
out.Key("bitangents");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
out.Element(ai.mBitangents[i].x);
out.Element(ai.mBitangents[i].y);
out.Element(ai.mBitangents[i].z);
}
out.EndArray();
}
if (ai.GetNumUVChannels()) {
out.Key("numuvcomponents");
out.StartArray();
for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) {
out.Element(ai.mNumUVComponents[n]);
}
out.EndArray();
out.Key("texturecoords");
out.StartArray();
for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) {
const unsigned int numc = ai.mNumUVComponents[n] ? ai.mNumUVComponents[n] : 2;
out.StartArray(true);
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
for (unsigned int c = 0; c < numc; ++c) {
out.Element(ai.mTextureCoords[n][i][c]);
}
}
out.EndArray();
}
out.EndArray();
}
if (ai.GetNumColorChannels()) {
out.Key("colors");
out.StartArray();
for (unsigned int n = 0; n < ai.GetNumColorChannels(); ++n) {
out.StartArray(true);
for (unsigned int i = 0; i < ai.mNumVertices; ++i) {
out.Element(ai.mColors[n][i].r);
out.Element(ai.mColors[n][i].g);
out.Element(ai.mColors[n][i].b);
out.Element(ai.mColors[n][i].a);
}
out.EndArray();
}
out.EndArray();
}
if (ai.mNumBones) {
out.Key("bones");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumBones; ++n) {
Write(out, *ai.mBones[n]);
}
out.EndArray();
}
out.Key("faces");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumFaces; ++n) {
Write(out, ai.mFaces[n]);
}
out.EndArray();
out.EndObj();
}
static void Write(JSONWriter &out, const aiNode &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("transformation");
Write(out, ai.mTransformation, false);
if (ai.mNumMeshes) {
out.Key("meshes");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumMeshes; ++n) {
out.Element(ai.mMeshes[n]);
}
out.EndArray();
}
if (ai.mNumChildren) {
out.Key("children");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumChildren; ++n) {
Write(out, *ai.mChildren[n]);
}
out.EndArray();
}
out.EndObj();
}
static void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("properties");
out.StartArray();
for (unsigned int i = 0; i < ai.mNumProperties; ++i) {
const aiMaterialProperty *const prop = ai.mProperties[i];
out.StartObj(true);
out.Key("key");
out.SimpleValue(prop->mKey);
out.Key("semantic");
out.SimpleValue(prop->mSemantic);
out.Key("index");
out.SimpleValue(prop->mIndex);
out.Key("type");
out.SimpleValue(prop->mType);
out.Key("value");
switch (prop->mType) {
case aiPTI_Float:
if (prop->mDataLength / sizeof(float) > 1) {
out.StartArray();
for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(float); ++ii) {
out.Element(reinterpret_cast<float *>(prop->mData)[ii]);
}
out.EndArray();
} else {
out.SimpleValue(*reinterpret_cast<float *>(prop->mData));
}
break;
case aiPTI_Double:
if (prop->mDataLength / sizeof(double) > 1) {
out.StartArray();
for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(double); ++ii) {
out.Element(reinterpret_cast<double*>(prop->mData)[ii]);
}
out.EndArray();
} else {
out.SimpleValue(*reinterpret_cast<double*>(prop->mData));
}
break;
case aiPTI_Integer:
if (prop->mDataLength / sizeof(int) > 1) {
out.StartArray();
for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(int); ++ii) {
out.Element(reinterpret_cast<int *>(prop->mData)[ii]);
}
out.EndArray();
} else {
out.SimpleValue(*reinterpret_cast<int *>(prop->mData));
}
break;
case aiPTI_String:
{
aiString s;
aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s);
out.SimpleValue(s);
}
break;
case aiPTI_Buffer:
{
// binary data is written as series of hex-encoded octets
out.SimpleValue(prop->mData, prop->mDataLength);
}
break;
default:
ai_assert(false);
}
out.EndObj();
}
out.EndArray();
out.EndObj();
}
static void Write(JSONWriter &out, const aiTexture &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("width");
out.SimpleValue(ai.mWidth);
out.Key("height");
out.SimpleValue(ai.mHeight);
out.Key("formathint");
out.SimpleValue(aiString(ai.achFormatHint));
out.Key("data");
if (!ai.mHeight) {
out.SimpleValue(ai.pcData, ai.mWidth);
} else {
out.StartArray();
for (unsigned int y = 0; y < ai.mHeight; ++y) {
out.StartArray(true);
for (unsigned int x = 0; x < ai.mWidth; ++x) {
const aiTexel &tx = ai.pcData[y * ai.mWidth + x];
out.StartArray(true);
out.Element(static_cast<unsigned int>(tx.r));
out.Element(static_cast<unsigned int>(tx.g));
out.Element(static_cast<unsigned int>(tx.b));
out.Element(static_cast<unsigned int>(tx.a));
out.EndArray();
}
out.EndArray();
}
out.EndArray();
}
out.EndObj();
}
static void Write(JSONWriter &out, const aiLight &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("type");
out.SimpleValue(ai.mType);
if (ai.mType == aiLightSource_SPOT || ai.mType == aiLightSource_UNDEFINED) {
out.Key("angleinnercone");
out.SimpleValue(ai.mAngleInnerCone);
out.Key("angleoutercone");
out.SimpleValue(ai.mAngleOuterCone);
}
out.Key("attenuationconstant");
out.SimpleValue(ai.mAttenuationConstant);
out.Key("attenuationlinear");
out.SimpleValue(ai.mAttenuationLinear);
out.Key("attenuationquadratic");
out.SimpleValue(ai.mAttenuationQuadratic);
out.Key("diffusecolor");
Write(out, ai.mColorDiffuse, false);
out.Key("specularcolor");
Write(out, ai.mColorSpecular, false);
out.Key("ambientcolor");
Write(out, ai.mColorAmbient, false);
if (ai.mType != aiLightSource_POINT) {
out.Key("direction");
Write(out, ai.mDirection, false);
}
if (ai.mType != aiLightSource_DIRECTIONAL) {
out.Key("position");
Write(out, ai.mPosition, false);
}
out.EndObj();
}
static void Write(JSONWriter &out, const aiNodeAnim &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mNodeName);
out.Key("prestate");
out.SimpleValue(ai.mPreState);
out.Key("poststate");
out.SimpleValue(ai.mPostState);
if (ai.mNumPositionKeys) {
out.Key("positionkeys");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumPositionKeys; ++n) {
const aiVectorKey &pos = ai.mPositionKeys[n];
out.StartArray(true);
out.Element(pos.mTime);
Write(out, pos.mValue);
out.EndArray();
}
out.EndArray();
}
if (ai.mNumRotationKeys) {
out.Key("rotationkeys");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumRotationKeys; ++n) {
const aiQuatKey &rot = ai.mRotationKeys[n];
out.StartArray(true);
out.Element(rot.mTime);
Write(out, rot.mValue);
out.EndArray();
}
out.EndArray();
}
if (ai.mNumScalingKeys) {
out.Key("scalingkeys");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumScalingKeys; ++n) {
const aiVectorKey &scl = ai.mScalingKeys[n];
out.StartArray(true);
out.Element(scl.mTime);
Write(out, scl.mValue);
out.EndArray();
}
out.EndArray();
}
out.EndObj();
}
static void Write(JSONWriter &out, const aiAnimation &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("tickspersecond");
out.SimpleValue(ai.mTicksPerSecond);
out.Key("duration");
out.SimpleValue(ai.mDuration);
out.Key("channels");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumChannels; ++n) {
Write(out, *ai.mChannels[n]);
}
out.EndArray();
out.EndObj();
}
static void Write(JSONWriter &out, const aiCamera &ai, bool is_elem = true) {
out.StartObj(is_elem);
out.Key("name");
out.SimpleValue(ai.mName);
out.Key("aspect");
out.SimpleValue(ai.mAspect);
out.Key("clipplanefar");
out.SimpleValue(ai.mClipPlaneFar);
out.Key("clipplanenear");
out.SimpleValue(ai.mClipPlaneNear);
out.Key("horizontalfov");
out.SimpleValue(ai.mHorizontalFOV);
out.Key("up");
Write(out, ai.mUp, false);
out.Key("lookat");
Write(out, ai.mLookAt, false);
out.EndObj();
}
static void WriteFormatInfo(JSONWriter &out) {
out.StartObj();
out.Key("format");
out.SimpleValue("\"assimp2json\"");
out.Key("version");
out.SimpleValue(CURRENT_FORMAT_VERSION);
out.EndObj();
}
static void Write(JSONWriter &out, const aiScene &ai) {
out.StartObj();
out.Key("__metadata__");
WriteFormatInfo(out);
out.Key("rootnode");
Write(out, *ai.mRootNode, false);
out.Key("flags");
out.SimpleValue(ai.mFlags);
if (ai.HasMeshes()) {
out.Key("meshes");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumMeshes; ++n) {
Write(out, *ai.mMeshes[n]);
}
out.EndArray();
}
if (ai.HasMaterials()) {
out.Key("materials");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumMaterials; ++n) {
Write(out, *ai.mMaterials[n]);
}
out.EndArray();
}
if (ai.HasAnimations()) {
out.Key("animations");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumAnimations; ++n) {
Write(out, *ai.mAnimations[n]);
}
out.EndArray();
}
if (ai.HasLights()) {
out.Key("lights");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumLights; ++n) {
Write(out, *ai.mLights[n]);
}
out.EndArray();
}
if (ai.HasCameras()) {
out.Key("cameras");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumCameras; ++n) {
Write(out, *ai.mCameras[n]);
}
out.EndArray();
}
if (ai.HasTextures()) {
out.Key("textures");
out.StartArray();
for (unsigned int n = 0; n < ai.mNumTextures; ++n) {
Write(out, *ai.mTextures[n]);
}
out.EndArray();
}
out.EndObj();
}
void ExportAssimp2Json(const char *file, Assimp::IOSystem *io, const aiScene *scene, const Assimp::ExportProperties *pProperties) {
std::unique_ptr<Assimp::IOStream> str(io->Open(file, "wt"));
if (!str) {
throw DeadlyExportError("could not open output file");
}
// get a copy of the scene so we can modify it
aiScene *scenecopy_tmp;
aiCopyScene(scene, &scenecopy_tmp);
try {
// split meshes so they fit into a 16 bit index buffer
MeshSplitter splitter;
splitter.SetLimit(1 << 16);
splitter.Execute(scenecopy_tmp);
// XXX Flag_WriteSpecialFloats is turned on by default, right now we don't have a configuration interface for exporters
unsigned int flags = JSONWriter::Flag_WriteSpecialFloats;
if (pProperties->GetPropertyBool("JSON_SKIP_WHITESPACES", false)) {
flags |= JSONWriter::Flag_SkipWhitespaces;
}
JSONWriter s(*str, flags);
Write(s, *scenecopy_tmp);
} catch (...) {
aiFreeScene(scenecopy_tmp);
throw;
}
aiFreeScene(scenecopy_tmp);
}
} // namespace Assimp
#endif // ASSIMP_BUILD_NO_ASSJSON_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,319 @@
/*
Assimp2Json
Copyright (c) 2011, Alexander C. Gessler
Licensed under a 3-clause BSD license. See the LICENSE file for more information.
*/
#include "mesh_splitter.h"
#include <assimp/scene.h>
// ----------------------------------------------------------------------------
// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process.
// it is refactored and the coding style is slightly improved, though.
// ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// Executes the post processing step on the given imported data.
void MeshSplitter::Execute( aiScene* pScene) {
std::vector<std::pair<aiMesh*, unsigned int> > source_mesh_map;
for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
SplitMesh(a, pScene->mMeshes[a],source_mesh_map);
}
const unsigned int size = static_cast<unsigned int>(source_mesh_map.size());
if (size != pScene->mNumMeshes) {
// it seems something has been split. rebuild the mesh list
delete[] pScene->mMeshes;
pScene->mNumMeshes = size;
pScene->mMeshes = new aiMesh*[size]();
for (unsigned int i = 0; i < size;++i) {
pScene->mMeshes[i] = source_mesh_map[i].first;
}
// now we need to update all nodes
UpdateNode(pScene->mRootNode,source_mesh_map);
}
}
// ------------------------------------------------------------------------------------------------
void MeshSplitter::UpdateNode(aiNode* pcNode, const std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) {
// TODO: should better use std::(multi)set for source_mesh_map.
// for every index in out list build a new entry
std::vector<unsigned int> aiEntries;
aiEntries.reserve(pcNode->mNumMeshes + 1);
for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) {
for (unsigned int a = 0, end = static_cast<unsigned int>(source_mesh_map.size()); a < end;++a) {
if (source_mesh_map[a].second == pcNode->mMeshes[i]) {
aiEntries.push_back(a);
}
}
}
// now build the new list
delete pcNode->mMeshes;
pcNode->mNumMeshes = static_cast<unsigned int>(aiEntries.size());
pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
for (unsigned int b = 0; b < pcNode->mNumMeshes;++b) {
pcNode->mMeshes[b] = aiEntries[b];
}
// recursively update children
for (unsigned int i = 0, end = pcNode->mNumChildren; i < end;++i) {
UpdateNode ( pcNode->mChildren[i], source_mesh_map );
}
}
static const unsigned int WAS_NOT_COPIED = 0xffffffff;
using PerVertexWeight = std::pair <unsigned int,float>;
using VertexWeightTable = std::vector <PerVertexWeight>;
// ------------------------------------------------------------------------------------------------
VertexWeightTable* ComputeVertexBoneWeightTable(const aiMesh* pMesh) {
if (!pMesh || !pMesh->mNumVertices || !pMesh->mNumBones) {
return nullptr;
}
VertexWeightTable* const avPerVertexWeights = new VertexWeightTable[pMesh->mNumVertices];
for (unsigned int i = 0; i < pMesh->mNumBones;++i) {
aiBone* bone = pMesh->mBones[i];
for (unsigned int a = 0; a < bone->mNumWeights;++a) {
const aiVertexWeight& weight = bone->mWeights[a];
avPerVertexWeights[weight.mVertexId].emplace_back(i,weight.mWeight);
}
}
return avPerVertexWeights;
}
// ------------------------------------------------------------------------------------------------
void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) {
// TODO: should better use std::(multi)set for source_mesh_map.
if (in_mesh->mNumVertices <= LIMIT) {
source_mesh_map.emplace_back(in_mesh,a);
return;
}
// build a per-vertex weight list if necessary
VertexWeightTable* avPerVertexWeights = ComputeVertexBoneWeightTable(in_mesh);
// we need to split this mesh into sub meshes. Estimate submesh size
const unsigned int sub_meshes = (in_mesh->mNumVertices / LIMIT) + 1;
// create a std::vector<unsigned int> to remember which vertices have already
// been copied and to which position (i.e. output index)
std::vector<unsigned int> was_copied_to;
was_copied_to.resize(in_mesh->mNumVertices,WAS_NOT_COPIED);
// Try to find a good estimate for the number of output faces
// per mesh. Add 12.5% as buffer
unsigned int size_estimated = in_mesh->mNumFaces / sub_meshes;
size_estimated += size_estimated / 8;
// now generate all submeshes
unsigned int base = 0;
while (true) {
const unsigned int out_vertex_index = LIMIT;
aiMesh* out_mesh = new aiMesh();
out_mesh->mNumVertices = 0;
out_mesh->mMaterialIndex = in_mesh->mMaterialIndex;
// the name carries the adjacency information between the meshes
out_mesh->mName = in_mesh->mName;
typedef std::vector<aiVertexWeight> BoneWeightList;
if (in_mesh->HasBones()) {
out_mesh->mBones = new aiBone*[in_mesh->mNumBones]();
}
// clear the temporary helper array
if (base) {
std::fill(was_copied_to.begin(), was_copied_to.end(), WAS_NOT_COPIED);
}
std::vector<aiFace> vFaces;
// reserve enough storage for most cases
if (in_mesh->HasPositions()) {
out_mesh->mVertices = new aiVector3D[out_vertex_index];
}
if (in_mesh->HasNormals()) {
out_mesh->mNormals = new aiVector3D[out_vertex_index];
}
if (in_mesh->HasTangentsAndBitangents()) {
out_mesh->mTangents = new aiVector3D[out_vertex_index];
out_mesh->mBitangents = new aiVector3D[out_vertex_index];
}
for (unsigned int c = 0; in_mesh->HasVertexColors(c);++c) {
out_mesh->mColors[c] = new aiColor4D[out_vertex_index];
}
for (unsigned int c = 0; in_mesh->HasTextureCoords(c);++c) {
out_mesh->mNumUVComponents[c] = in_mesh->mNumUVComponents[c];
out_mesh->mTextureCoords[c] = new aiVector3D[out_vertex_index];
}
vFaces.reserve(size_estimated);
// (we will also need to copy the array of indices)
while (base < in_mesh->mNumFaces) {
const unsigned int iNumIndices = in_mesh->mFaces[base].mNumIndices;
// doesn't catch degenerates but is quite fast
unsigned int iNeed = 0;
for (unsigned int v = 0; v < iNumIndices;++v) {
unsigned int index = in_mesh->mFaces[base].mIndices[v];
// check whether we do already have this vertex
if (WAS_NOT_COPIED == was_copied_to[index]) {
iNeed++;
}
}
if (out_mesh->mNumVertices + iNeed > out_vertex_index) {
// don't use this face
break;
}
vFaces.emplace_back();
aiFace& rFace = vFaces.back();
// setup face type and number of indices
rFace.mNumIndices = iNumIndices;
rFace.mIndices = new unsigned int[iNumIndices];
// need to update the output primitive types
switch (rFace.mNumIndices)
{
case 1:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
break;
case 2:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
break;
case 3:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
break;
default:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
}
// and copy the contents of the old array, offset them by current base
for (unsigned int v = 0; v < iNumIndices;++v) {
const unsigned int index = in_mesh->mFaces[base].mIndices[v];
// check whether we do already have this vertex
if (WAS_NOT_COPIED != was_copied_to[index]) {
rFace.mIndices[v] = was_copied_to[index];
continue;
}
// copy positions
out_mesh->mVertices[out_mesh->mNumVertices] = (in_mesh->mVertices[index]);
// copy normals
if (in_mesh->HasNormals()) {
out_mesh->mNormals[out_mesh->mNumVertices] = (in_mesh->mNormals[index]);
}
// copy tangents/bi-tangents
if (in_mesh->HasTangentsAndBitangents()) {
out_mesh->mTangents[out_mesh->mNumVertices] = (in_mesh->mTangents[index]);
out_mesh->mBitangents[out_mesh->mNumVertices] = (in_mesh->mBitangents[index]);
}
// texture coordinates
for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) {
if (in_mesh->HasTextureCoords( c)) {
out_mesh->mTextureCoords[c][out_mesh->mNumVertices] = in_mesh->mTextureCoords[c][index];
}
}
// vertex colors
for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) {
if (in_mesh->HasVertexColors( c)) {
out_mesh->mColors[c][out_mesh->mNumVertices] = in_mesh->mColors[c][index];
}
}
// check whether we have bone weights assigned to this vertex
rFace.mIndices[v] = out_mesh->mNumVertices;
if (avPerVertexWeights) {
VertexWeightTable& table = avPerVertexWeights[ out_mesh->mNumVertices ];
for (VertexWeightTable::const_iterator iter = table.begin(), end = table.end(); iter != end;++iter) {
// allocate the bone weight array if necessary and store it in the mBones field (HACK!)
BoneWeightList* weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[(*iter).first]);
if (!weight_list) {
weight_list = new BoneWeightList();
out_mesh->mBones[(*iter).first] = reinterpret_cast<aiBone*>(weight_list);
}
weight_list->push_back(aiVertexWeight(out_mesh->mNumVertices,(*iter).second));
}
}
was_copied_to[index] = out_mesh->mNumVertices;
out_mesh->mNumVertices++;
}
base++;
if(out_mesh->mNumVertices == out_vertex_index) {
// break here. The face is only added if it was complete
break;
}
}
// check which bones we'll need to create for this submesh
if (in_mesh->HasBones()) {
aiBone** ppCurrent = out_mesh->mBones;
for (unsigned int k = 0; k < in_mesh->mNumBones;++k) {
// check whether the bone exists
BoneWeightList* const weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[k]);
if (weight_list) {
const aiBone* const bone_in = in_mesh->mBones[k];
aiBone* const bone_out = new aiBone();
*ppCurrent++ = bone_out;
bone_out->mName = aiString(bone_in->mName);
bone_out->mOffsetMatrix =bone_in->mOffsetMatrix;
bone_out->mNumWeights = (unsigned int)weight_list->size();
bone_out->mWeights = new aiVertexWeight[bone_out->mNumWeights];
// copy the vertex weights
::memcpy(bone_out->mWeights, &(*weight_list)[0],bone_out->mNumWeights * sizeof(aiVertexWeight));
delete weight_list;
out_mesh->mNumBones++;
}
}
}
// copy the face list to the mesh
out_mesh->mFaces = new aiFace[vFaces.size()];
out_mesh->mNumFaces = (unsigned int)vFaces.size();
for (unsigned int p = 0; p < out_mesh->mNumFaces;++p) {
out_mesh->mFaces[p] = vFaces[p];
}
// add the newly created mesh to the list
source_mesh_map.emplace_back(out_mesh,a);
if (base == in_mesh->mNumFaces) {
break;
}
}
// delete the per-vertex weight list again
delete[] avPerVertexWeights;
// now delete the old mesh data
delete in_mesh;
}

View File

@@ -0,0 +1,52 @@
/*
Assimp2Json
Copyright (c) 2011, Alexander C. Gessler
Licensed under a 3-clause BSD license. See the LICENSE file for more information.
*/
#ifndef INCLUDED_MESH_SPLITTER
#define INCLUDED_MESH_SPLITTER
// ----------------------------------------------------------------------------
// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process.
// it is refactored and the coding style is slightly improved, though.
// ----------------------------------------------------------------------------
#include <vector>
struct aiScene;
struct aiMesh;
struct aiNode;
// ---------------------------------------------------------------------------
/** Splits meshes of unique vertices into meshes with no more vertices than
* a given, configurable threshold value.
*/
class MeshSplitter {
public:
unsigned int LIMIT;
void SetLimit(unsigned int l) {
LIMIT = l;
}
unsigned int GetLimit() const {
return LIMIT;
}
// -------------------------------------------------------------------
/** Executes the post processing step on the given imported data.
* At the moment a process is not supposed to fail.
* @param pScene The imported data to work at.
*/
void Execute(aiScene *pScene);
private:
void UpdateNode(aiNode *pcNode, const std::vector<std::pair<aiMesh *, unsigned int>> &source_mesh_map);
void SplitMesh(unsigned int index, aiMesh *mesh, std::vector<std::pair<aiMesh *, unsigned int>> &source_mesh_map);
};
#endif // INCLUDED_MESH_SPLITTER

View File

@@ -0,0 +1,67 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssxmlExporter.cpp
* ASSXML exporter main code
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
#include "AssxmlFileWriter.h"
#include <assimp/IOSystem.hpp>
#include <assimp/Exporter.hpp>
namespace Assimp {
void ExportSceneAssxml(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
{
DumpSceneToAssxml(
pFile,
"\0", // command(s)
pIOSystem,
pScene,
false); // shortened?
}
} // end of namespace Assimp
#endif // ASSIMP_BUILD_NO_ASSXML_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View File

@@ -0,0 +1,51 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssxmlExporter.h
* ASSXML Exporter Main Header
*/
#pragma once
#ifndef AI_ASSXMLEXPORTER_H_INC
#define AI_ASSXMLEXPORTER_H_INC
// nothing really needed here - reserved for future use like properties
#endif

View File

@@ -0,0 +1,663 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssxmlFileWriter.cpp
* @brief Implementation of Assxml file writer.
*/
#include "AssxmlFileWriter.h"
#include "PostProcessing/ProcessHelper.h"
#include <assimp/version.h>
#include <assimp/Exporter.hpp>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
#include <stdarg.h>
#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
#include <zlib.h>
#else
#include <contrib/zlib/zlib.h>
#endif
#include <stdio.h>
#include <time.h>
#include <memory>
using namespace Assimp;
namespace Assimp {
namespace AssxmlFileWriter {
// -----------------------------------------------------------------------------------
static int ioprintf(IOStream *io, const char *format, ...) {
using namespace std;
if (nullptr == io) {
return -1;
}
static const int Size = 4096;
char sz[Size] = {};
va_list va;
va_start(va, format);
const unsigned int nSize = vsnprintf(sz, Size - 1, format, va);
ai_assert(nSize < Size);
va_end(va);
io->Write(sz, sizeof(char), nSize);
return nSize;
}
// -----------------------------------------------------------------------------------
// Convert a name to standard XML format
static void ConvertName(aiString &out, const aiString &in) {
out.length = 0;
for (unsigned int i = 0; i < in.length; ++i) {
switch (in.data[i]) {
case '<':
out.Append("&lt;");
break;
case '>':
out.Append("&gt;");
break;
case '&':
out.Append("&amp;");
break;
case '\"':
out.Append("&quot;");
break;
case '\'':
out.Append("&apos;");
break;
default:
out.data[out.length++] = in.data[i];
}
}
out.data[out.length] = 0;
}
// -----------------------------------------------------------------------------------
// Write a single node as text dump
static void WriteNode(const aiNode *node, IOStream *io, unsigned int depth) {
char prefix[512];
for (unsigned int i = 0; i < depth; ++i)
prefix[i] = '\t';
prefix[depth] = '\0';
const aiMatrix4x4 &m = node->mTransformation;
aiString name;
ConvertName(name, node->mName);
ioprintf(io, "%s<Node name=\"%s\"> \n"
"%s\t<Matrix4> \n"
"%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
"%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
"%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
"%s\t\t%0 6f %0 6f %0 6f %0 6f\n"
"%s\t</Matrix4> \n",
prefix, name.data, prefix,
prefix, m.a1, m.a2, m.a3, m.a4,
prefix, m.b1, m.b2, m.b3, m.b4,
prefix, m.c1, m.c2, m.c3, m.c4,
prefix, m.d1, m.d2, m.d3, m.d4, prefix);
if (node->mNumMeshes) {
ioprintf(io, "%s\t<MeshRefs num=\"%u\">\n%s\t",
prefix, node->mNumMeshes, prefix);
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
ioprintf(io, "%u ", node->mMeshes[i]);
}
ioprintf(io, "\n%s\t</MeshRefs>\n", prefix);
}
if (node->mNumChildren) {
ioprintf(io, "%s\t<NodeList num=\"%u\">\n",
prefix, node->mNumChildren);
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
WriteNode(node->mChildren[i], io, depth + 2);
}
ioprintf(io, "%s\t</NodeList>\n", prefix);
}
ioprintf(io, "%s</Node>\n", prefix);
}
// -----------------------------------------------------------------------------------
// Some chunks of text will need to be encoded for XML
// http://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string#5665377
static std::string encodeXML(const std::string &data) {
std::string buffer;
buffer.reserve(data.size());
for (size_t pos = 0; pos != data.size(); ++pos) {
switch (data[pos]) {
case '&': buffer.append("&amp;"); break;
case '\"': buffer.append("&quot;"); break;
case '\'': buffer.append("&apos;"); break;
case '<': buffer.append("&lt;"); break;
case '>': buffer.append("&gt;"); break;
default: buffer.append(&data[pos], 1); break;
}
}
return buffer;
}
// -----------------------------------------------------------------------------------
// Write a text model dump
static void WriteDump(const char *pFile, const char *cmd, const aiScene *scene, IOStream *io, bool shortened) {
time_t tt = ::time(nullptr);
#if _WIN32
tm *p = gmtime(&tt);
#else
struct tm now;
tm *p = gmtime_r(&tt, &now);
#endif
ai_assert(nullptr != p);
std::string c = cmd;
std::string::size_type s;
// https://sourceforge.net/tracker/?func=detail&aid=3167364&group_id=226462&atid=1067632
// -- not allowed in XML comments
while ((s = c.find("--")) != std::string::npos) {
c[s] = '?';
}
// write header
std::string header(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<ASSIMP format_id=\"1\">\n\n"
"<!-- XML Model dump produced by assimp dump\n"
" Library version: %u.%u.%u\n"
" Source: %s\n"
" Command line: %s\n"
" %s\n"
"-->"
" \n\n"
"<Scene flags=\"%u\" postprocessing=\"%u\">\n");
const unsigned int majorVersion(aiGetVersionMajor());
const unsigned int minorVersion(aiGetVersionMinor());
const unsigned int rev(aiGetVersionRevision());
const char *curtime = asctime(p);
ioprintf(io, header.c_str(), majorVersion, minorVersion, rev, pFile, c.c_str(), curtime, scene->mFlags, 0u);
// write the node graph
WriteNode(scene->mRootNode, io, 0);
#if 0
// write cameras
for (unsigned int i = 0; i < scene->mNumCameras;++i) {
aiCamera* cam = scene->mCameras[i];
ConvertName(name,cam->mName);
// camera header
ioprintf(io,"\t<Camera parent=\"%s\">\n"
"\t\t<Vector3 name=\"up\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Vector3 name=\"lookat\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Vector3 name=\"pos\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Float name=\"fov\" > %f </Float>\n"
"\t\t<Float name=\"aspect\" > %f </Float>\n"
"\t\t<Float name=\"near_clip\" > %f </Float>\n"
"\t\t<Float name=\"far_clip\" > %f </Float>\n"
"\t</Camera>\n",
name.data,
cam->mUp.x,cam->mUp.y,cam->mUp.z,
cam->mLookAt.x,cam->mLookAt.y,cam->mLookAt.z,
cam->mPosition.x,cam->mPosition.y,cam->mPosition.z,
cam->mHorizontalFOV,cam->mAspect,cam->mClipPlaneNear,cam->mClipPlaneFar,i);
}
// write lights
for (unsigned int i = 0; i < scene->mNumLights;++i) {
aiLight* l = scene->mLights[i];
ConvertName(name,l->mName);
// light header
ioprintf(io,"\t<Light parent=\"%s\"> type=\"%s\"\n"
"\t\t<Vector3 name=\"diffuse\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Vector3 name=\"specular\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Vector3 name=\"ambient\" > %0 8f %0 8f %0 8f </Vector3>\n",
name.data,
(l->mType == aiLightSource_DIRECTIONAL ? "directional" :
(l->mType == aiLightSource_POINT ? "point" : "spot" )),
l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b,
l->mColorSpecular.r,l->mColorSpecular.g,l->mColorSpecular.b,
l->mColorAmbient.r, l->mColorAmbient.g, l->mColorAmbient.b);
if (l->mType != aiLightSource_DIRECTIONAL) {
ioprintf(io,
"\t\t<Vector3 name=\"pos\" > %0 8f %0 8f %0 8f </Vector3>\n"
"\t\t<Float name=\"atten_cst\" > %f </Float>\n"
"\t\t<Float name=\"atten_lin\" > %f </Float>\n"
"\t\t<Float name=\"atten_sqr\" > %f </Float>\n",
l->mPosition.x,l->mPosition.y,l->mPosition.z,
l->mAttenuationConstant,l->mAttenuationLinear,l->mAttenuationQuadratic);
}
if (l->mType != aiLightSource_POINT) {
ioprintf(io,
"\t\t<Vector3 name=\"lookat\" > %0 8f %0 8f %0 8f </Vector3>\n",
l->mDirection.x,l->mDirection.y,l->mDirection.z);
}
if (l->mType == aiLightSource_SPOT) {
ioprintf(io,
"\t\t<Float name=\"cone_out\" > %f </Float>\n"
"\t\t<Float name=\"cone_inn\" > %f </Float>\n",
l->mAngleOuterCone,l->mAngleInnerCone);
}
ioprintf(io,"\t</Light>\n");
}
#endif
aiString name;
// write textures
if (scene->mNumTextures) {
ioprintf(io, "<TextureList num=\"%u\">\n", scene->mNumTextures);
for (unsigned int i = 0; i < scene->mNumTextures; ++i) {
aiTexture *tex = scene->mTextures[i];
bool compressed = (tex->mHeight == 0);
// mesh header
std::string texName = "unknown";
if (tex->mFilename.length != 0u) {
texName = tex->mFilename.data;
}
ioprintf(io, "\t<Texture name=\"%s\" width=\"%u\" height=\"%u\" compressed=\"%s\"> \n", texName.c_str(),
(compressed ? -1 : tex->mWidth), (compressed ? -1 : tex->mHeight),
(compressed ? "true" : "false"));
if (compressed) {
ioprintf(io, "\t\t<Data length=\"%u\"> \n", tex->mWidth);
if (!shortened) {
for (unsigned int n = 0; n < tex->mWidth; ++n) {
ioprintf(io, "\t\t\t%2x", reinterpret_cast<uint8_t *>(tex->pcData)[n]);
if (n && !(n % 50)) {
ioprintf(io, "\n");
}
}
}
} else if (!shortened) {
ioprintf(io, "\t\t<Data length=\"%u\"> \n", tex->mWidth * tex->mHeight * 4);
// const unsigned int width = (unsigned int)std::log10((double)std::max(tex->mHeight,tex->mWidth))+1;
for (unsigned int y = 0; y < tex->mHeight; ++y) {
for (unsigned int x = 0; x < tex->mWidth; ++x) {
aiTexel *tx = tex->pcData + y * tex->mWidth + x;
unsigned int r = tx->r, g = tx->g, b = tx->b, a = tx->a;
ioprintf(io, "\t\t\t%2x %2x %2x %2x", r, g, b, a);
// group by four for readability
if (0 == (x + y * tex->mWidth) % 4) {
ioprintf(io, "\n");
}
}
}
}
ioprintf(io, "\t\t</Data>\n\t</Texture>\n");
}
ioprintf(io, "</TextureList>\n");
}
// write materials
if (scene->mNumMaterials) {
ioprintf(io, "<MaterialList num=\"%u\">\n", scene->mNumMaterials);
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
const aiMaterial *mat = scene->mMaterials[i];
ioprintf(io, "\t<Material>\n");
ioprintf(io, "\t\t<MatPropertyList num=\"%u\">\n", mat->mNumProperties);
for (unsigned int n = 0; n < mat->mNumProperties; ++n) {
const aiMaterialProperty *prop = mat->mProperties[n];
auto sz = "";
if (prop->mType == aiPTI_Float) {
sz = "float";
} else if (prop->mType == aiPTI_Integer) {
sz = "integer";
} else if (prop->mType == aiPTI_String) {
sz = "string";
} else if (prop->mType == aiPTI_Buffer) {
sz = "binary_buffer";
}
ioprintf(io, "\t\t\t<MatProperty key=\"%s\" \n\t\t\ttype=\"%s\" tex_usage=\"%s\" tex_index=\"%u\"",
prop->mKey.data, sz,
::aiTextureTypeToString((aiTextureType)prop->mSemantic), prop->mIndex);
if (prop->mType == aiPTI_Float) {
ioprintf(io, " size=\"%i\">\n\t\t\t\t",
static_cast<int>(prop->mDataLength / sizeof(float)));
for (unsigned int pp = 0; pp < prop->mDataLength / sizeof(float); ++pp) {
ioprintf(io, "%f ", *((float *)(prop->mData + pp * sizeof(float))));
}
} else if (prop->mType == aiPTI_Integer) {
ioprintf(io, " size=\"%i\">\n\t\t\t\t",
static_cast<int>(prop->mDataLength / sizeof(int)));
for (unsigned int pp = 0; pp < prop->mDataLength / sizeof(int); ++pp) {
ioprintf(io, "%i ", *((int *)(prop->mData + pp * sizeof(int))));
}
} else if (prop->mType == aiPTI_Buffer) {
ioprintf(io, " size=\"%i\">\n\t\t\t\t",
static_cast<int>(prop->mDataLength));
for (unsigned int pp = 0; pp < prop->mDataLength; ++pp) {
ioprintf(io, "%2x ", prop->mData[pp]);
if (pp && 0 == pp % 30) {
ioprintf(io, "\n\t\t\t\t");
}
}
} else if (prop->mType == aiPTI_String) {
ioprintf(io, ">\n\t\t\t\t\"%s\"", encodeXML(prop->mData + 4).c_str() /* skip length */);
}
ioprintf(io, "\n\t\t\t</MatProperty>\n");
}
ioprintf(io, "\t\t</MatPropertyList>\n");
ioprintf(io, "\t</Material>\n");
}
ioprintf(io, "</MaterialList>\n");
}
// write animations
if (scene->mNumAnimations) {
ioprintf(io, "<AnimationList num=\"%u\">\n", scene->mNumAnimations);
for (unsigned int i = 0; i < scene->mNumAnimations; ++i) {
aiAnimation *anim = scene->mAnimations[i];
// anim header
ConvertName(name, anim->mName);
ioprintf(io, "\t<Animation name=\"%s\" duration=\"%e\" tick_cnt=\"%e\">\n",
name.data, anim->mDuration, anim->mTicksPerSecond);
// write bone animation channels
if (anim->mNumChannels) {
ioprintf(io, "\t\t<NodeAnimList num=\"%u\">\n", anim->mNumChannels);
for (unsigned int n = 0; n < anim->mNumChannels; ++n) {
aiNodeAnim *nd = anim->mChannels[n];
// node anim header
ConvertName(name, nd->mNodeName);
ioprintf(io, "\t\t\t<NodeAnim node=\"%s\">\n", name.data);
if (!shortened) {
// write position keys
if (nd->mNumPositionKeys) {
ioprintf(io, "\t\t\t\t<PositionKeyList num=\"%u\">\n", nd->mNumPositionKeys);
for (unsigned int a = 0; a < nd->mNumPositionKeys; ++a) {
aiVectorKey *vc = nd->mPositionKeys + a;
ioprintf(io, "\t\t\t\t\t<PositionKey time=\"%e\">\n"
"\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</PositionKey>\n",
vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z);
}
ioprintf(io, "\t\t\t\t</PositionKeyList>\n");
}
// write scaling keys
if (nd->mNumScalingKeys) {
ioprintf(io, "\t\t\t\t<ScalingKeyList num=\"%u\">\n", nd->mNumScalingKeys);
for (unsigned int a = 0; a < nd->mNumScalingKeys; ++a) {
aiVectorKey *vc = nd->mScalingKeys + a;
ioprintf(io, "\t\t\t\t\t<ScalingKey time=\"%e\">\n"
"\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</ScalingKey>\n",
vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z);
}
ioprintf(io, "\t\t\t\t</ScalingKeyList>\n");
}
// write rotation keys
if (nd->mNumRotationKeys) {
ioprintf(io, "\t\t\t\t<RotationKeyList num=\"%u\">\n", nd->mNumRotationKeys);
for (unsigned int a = 0; a < nd->mNumRotationKeys; ++a) {
aiQuatKey *vc = nd->mRotationKeys + a;
ioprintf(io, "\t\t\t\t\t<RotationKey time=\"%e\">\n"
"\t\t\t\t\t\t%0 8f %0 8f %0 8f %0 8f\n\t\t\t\t\t</RotationKey>\n",
vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z, vc->mValue.w);
}
ioprintf(io, "\t\t\t\t</RotationKeyList>\n");
}
}
ioprintf(io, "\t\t\t</NodeAnim>\n");
}
ioprintf(io, "\t\t</NodeAnimList>\n");
}
ioprintf(io, "\t</Animation>\n");
}
ioprintf(io, "</AnimationList>\n");
}
// write meshes
if (scene->mNumMeshes) {
ioprintf(io, "<MeshList num=\"%u\">\n", scene->mNumMeshes);
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
aiMesh *mesh = scene->mMeshes[i];
// const unsigned int width = (unsigned int)std::log10((double)mesh->mNumVertices)+1;
// mesh header
ioprintf(io, "\t<Mesh types=\"%s %s %s %s\" material_index=\"%u\">\n",
(mesh->mPrimitiveTypes & aiPrimitiveType_POINT ? "points" : ""),
(mesh->mPrimitiveTypes & aiPrimitiveType_LINE ? "lines" : ""),
(mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ? "triangles" : ""),
(mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON ? "polygons" : ""),
mesh->mMaterialIndex);
// bones
if (mesh->mNumBones) {
ioprintf(io, "\t\t<BoneList num=\"%u\">\n", mesh->mNumBones);
for (unsigned int n = 0; n < mesh->mNumBones; ++n) {
aiBone *bone = mesh->mBones[n];
ConvertName(name, bone->mName);
// bone header
ioprintf(io, "\t\t\t<Bone name=\"%s\">\n"
"\t\t\t\t<Matrix4> \n"
"\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
"\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
"\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
"\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n"
"\t\t\t\t</Matrix4> \n",
name.data,
bone->mOffsetMatrix.a1, bone->mOffsetMatrix.a2, bone->mOffsetMatrix.a3, bone->mOffsetMatrix.a4,
bone->mOffsetMatrix.b1, bone->mOffsetMatrix.b2, bone->mOffsetMatrix.b3, bone->mOffsetMatrix.b4,
bone->mOffsetMatrix.c1, bone->mOffsetMatrix.c2, bone->mOffsetMatrix.c3, bone->mOffsetMatrix.c4,
bone->mOffsetMatrix.d1, bone->mOffsetMatrix.d2, bone->mOffsetMatrix.d3, bone->mOffsetMatrix.d4);
if (!shortened && bone->mNumWeights) {
ioprintf(io, "\t\t\t\t<WeightList num=\"%u\">\n", bone->mNumWeights);
// bone weights
for (unsigned int a = 0; a < bone->mNumWeights; ++a) {
aiVertexWeight *wght = bone->mWeights + a;
ioprintf(io, "\t\t\t\t\t<Weight index=\"%u\">\n\t\t\t\t\t\t%f\n\t\t\t\t\t</Weight>\n",
wght->mVertexId, wght->mWeight);
}
ioprintf(io, "\t\t\t\t</WeightList>\n");
}
ioprintf(io, "\t\t\t</Bone>\n");
}
ioprintf(io, "\t\t</BoneList>\n");
}
// faces
if (!shortened && mesh->mNumFaces) {
ioprintf(io, "\t\t<FaceList num=\"%u\">\n", mesh->mNumFaces);
for (unsigned int n = 0; n < mesh->mNumFaces; ++n) {
aiFace &f = mesh->mFaces[n];
ioprintf(io, "\t\t\t<Face num=\"%u\">\n"
"\t\t\t\t",
f.mNumIndices);
for (unsigned int j = 0; j < f.mNumIndices; ++j)
ioprintf(io, "%u ", f.mIndices[j]);
ioprintf(io, "\n\t\t\t</Face>\n");
}
ioprintf(io, "\t\t</FaceList>\n");
}
// vertex positions
if (mesh->HasPositions()) {
ioprintf(io, "\t\t<Positions num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices);
if (!shortened) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n",
mesh->mVertices[n].x,
mesh->mVertices[n].y,
mesh->mVertices[n].z);
}
}
ioprintf(io, "\t\t</Positions>\n");
}
// vertex normals
if (mesh->HasNormals()) {
ioprintf(io, "\t\t<Normals num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices);
if (!shortened) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n",
mesh->mNormals[n].x,
mesh->mNormals[n].y,
mesh->mNormals[n].z);
}
}
ioprintf(io, "\t\t</Normals>\n");
}
// vertex tangents and bitangents
if (mesh->HasTangentsAndBitangents()) {
ioprintf(io, "\t\t<Tangents num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices);
if (!shortened) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n",
mesh->mTangents[n].x,
mesh->mTangents[n].y,
mesh->mTangents[n].z);
}
}
ioprintf(io, "\t\t</Tangents>\n");
ioprintf(io, "\t\t<Bitangents num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices);
if (!shortened) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n",
mesh->mBitangents[n].x,
mesh->mBitangents[n].y,
mesh->mBitangents[n].z);
}
}
ioprintf(io, "\t\t</Bitangents>\n");
}
// texture coordinates
for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
if (!mesh->mTextureCoords[a])
break;
ioprintf(io, "\t\t<TextureCoords num=\"%u\" set=\"%u\" name=\"%s\" num_components=\"%u\"> \n",
mesh->mNumVertices,
a,
(mesh->HasTextureCoordsName(a) ? mesh->GetTextureCoordsName(a)->C_Str() : ""),
mesh->mNumUVComponents[a]);
if (!shortened) {
if (mesh->mNumUVComponents[a] == 3) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n",
mesh->mTextureCoords[a][n].x,
mesh->mTextureCoords[a][n].y,
mesh->mTextureCoords[a][n].z);
}
} else {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f\n",
mesh->mTextureCoords[a][n].x,
mesh->mTextureCoords[a][n].y);
}
}
}
ioprintf(io, "\t\t</TextureCoords>\n");
}
// vertex colors
for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
if (!mesh->mColors[a])
break;
ioprintf(io, "\t\t<Colors num=\"%u\" set=\"%u\" num_components=\"4\"> \n", mesh->mNumVertices, a);
if (!shortened) {
for (unsigned int n = 0; n < mesh->mNumVertices; ++n) {
ioprintf(io, "\t\t%0 8f %0 8f %0 8f %0 8f\n",
mesh->mColors[a][n].r,
mesh->mColors[a][n].g,
mesh->mColors[a][n].b,
mesh->mColors[a][n].a);
}
}
ioprintf(io, "\t\t</Colors>\n");
}
ioprintf(io, "\t</Mesh>\n");
}
ioprintf(io, "</MeshList>\n");
}
ioprintf(io, "</Scene>\n</ASSIMP>");
}
} // end of namespace AssxmlFileWriter
void DumpSceneToAssxml(
const char *pFile, const char *cmd, IOSystem *pIOSystem,
const aiScene *pScene, bool shortened) {
std::unique_ptr<IOStream> file(pIOSystem->Open(pFile, "wt"));
if (!file) {
throw std::runtime_error("Unable to open output file " + std::string(pFile) + '\n');
}
AssxmlFileWriter::WriteDump(pFile, cmd, pScene, file.get(), shortened);
}
} // end of namespace Assimp

View File

@@ -0,0 +1,64 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file AssxmlFileWriter.h
* @brief Declaration of Assxml file writer.
*/
#pragma once
#ifndef AI_ASSXMLFILEWRITER_H_INC
#define AI_ASSXMLFILEWRITER_H_INC
#include <assimp/defs.h>
#include <assimp/scene.h>
#include <assimp/IOSystem.hpp>
namespace Assimp {
void ASSIMP_API DumpSceneToAssxml(
const char* pFile,
const char* cmd,
IOSystem* pIOSystem,
const aiScene* pScene,
bool shortened);
}
#endif // AI_ASSXMLFILEWRITER_H_INC

View File

@@ -0,0 +1,743 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
/** @file B3DImporter.cpp
* @brief Implementation of the b3d importer class
*/
#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
// internal headers
#include "B3DImporter.h"
#include "PostProcessing/ConvertToLHProcess.h"
#include "PostProcessing/TextureTransform.h"
#include <assimp/StringUtils.h>
#include <assimp/anim.h>
#include <assimp/importerdesc.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/IOSystem.hpp>
#include <memory>
namespace Assimp {
using namespace std;
static constexpr aiImporterDesc desc = {
"BlitzBasic 3D Importer",
"",
"",
"http://www.blitzbasic.com/",
aiImporterFlags_SupportBinaryFlavour,
0,
0,
0,
0,
"b3d"
};
#ifdef _MSC_VER
#pragma warning(disable : 4018)
#endif
// #define DEBUG_B3D
template <typename T>
void DeleteAllBarePointers(std::vector<T> &x) {
for (auto p : x) {
delete p;
}
}
B3DImporter::~B3DImporter() = default;
// ------------------------------------------------------------------------------------------------
bool B3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
size_t pos = pFile.find_last_of('.');
if (pos == string::npos) {
return false;
}
string ext = pFile.substr(pos + 1);
if (ext.size() != 3) {
return false;
}
return (ext[0] == 'b' || ext[0] == 'B') && (ext[1] == '3') && (ext[2] == 'd' || ext[2] == 'D');
}
// ------------------------------------------------------------------------------------------------
// Loader meta information
const aiImporterDesc *B3DImporter::GetInfo() const {
return &desc;
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
// Check whether we can read from the file
if (file == nullptr) {
throw DeadlyImportError("Failed to open B3D file ", pFile, ".");
}
// check whether the .b3d file is large enough to contain
// at least one chunk.
size_t fileSize = file->FileSize();
if (fileSize < 8) {
throw DeadlyImportError("B3D File is too small.");
}
_pos = 0;
_buf.resize(fileSize);
file->Read(&_buf[0], 1, fileSize);
_stack.clear();
ReadBB3D(pScene);
}
// ------------------------------------------------------------------------------------------------
AI_WONT_RETURN void B3DImporter::Oops() {
throw DeadlyImportError("B3D Importer - INTERNAL ERROR");
}
// ------------------------------------------------------------------------------------------------
AI_WONT_RETURN void B3DImporter::Fail(const string &str) {
#ifdef DEBUG_B3D
ASSIMP_LOG_ERROR("Error in B3D file data: ", str);
#endif
throw DeadlyImportError("B3D Importer - error in B3D file data: ", str);
}
// ------------------------------------------------------------------------------------------------
int B3DImporter::ReadByte() {
if (_pos >= _buf.size()) {
Fail("EOF");
}
return _buf[_pos++];
}
// ------------------------------------------------------------------------------------------------
int B3DImporter::ReadInt() {
if (_pos + 4 > _buf.size()) {
Fail("EOF");
}
int n;
memcpy(&n, &_buf[_pos], 4);
_pos += 4;
return n;
}
// ------------------------------------------------------------------------------------------------
float B3DImporter::ReadFloat() {
if (_pos + 4 > _buf.size()) {
Fail("EOF");
}
float n;
memcpy(&n, &_buf[_pos], 4);
_pos += 4;
return n;
}
// ------------------------------------------------------------------------------------------------
aiVector2D B3DImporter::ReadVec2() {
float x = ReadFloat();
float y = ReadFloat();
return aiVector2D(x, y);
}
// ------------------------------------------------------------------------------------------------
aiVector3D B3DImporter::ReadVec3() {
float x = ReadFloat();
float y = ReadFloat();
float z = ReadFloat();
return aiVector3D(x, y, z);
}
// ------------------------------------------------------------------------------------------------
aiQuaternion B3DImporter::ReadQuat() {
// (aramis_acg) Fix to adapt the loader to changed quat orientation
float w = -ReadFloat();
float x = ReadFloat();
float y = ReadFloat();
float z = ReadFloat();
return aiQuaternion(w, x, y, z);
}
// ------------------------------------------------------------------------------------------------
string B3DImporter::ReadString() {
if (_pos > _buf.size()) {
Fail("EOF");
}
string str;
while (_pos < _buf.size()) {
char c = (char)ReadByte();
if (!c) {
return str;
}
str += c;
}
return string();
}
// ------------------------------------------------------------------------------------------------
string B3DImporter::ReadChunk() {
string tag;
for (int i = 0; i < 4; ++i) {
tag += char(ReadByte());
}
#ifdef DEBUG_B3D
ASSIMP_LOG_DEBUG("ReadChunk: ", tag);
#endif
unsigned sz = (unsigned)ReadInt();
_stack.push_back(_pos + sz);
return tag;
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ExitChunk() {
_pos = _stack.back();
_stack.pop_back();
}
// ------------------------------------------------------------------------------------------------
size_t B3DImporter::ChunkSize() {
return _stack.back() - _pos;
}
// ------------------------------------------------------------------------------------------------
template <class T>
T *B3DImporter::to_array(const vector<T> &v) {
if (v.empty()) {
return nullptr;
}
T *p = new T[v.size()];
for (size_t i = 0; i < v.size(); ++i) {
p[i] = v[i];
}
return p;
}
// ------------------------------------------------------------------------------------------------
template <class T>
T **unique_to_array(vector<std::unique_ptr<T>> &v) {
if (v.empty()) {
return nullptr;
}
T **p = new T *[v.size()];
for (size_t i = 0; i < v.size(); ++i) {
p[i] = v[i].release();
}
return p;
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadTEXS() {
while (ChunkSize()) {
string name = ReadString();
/*int flags=*/ReadInt();
/*int blend=*/ReadInt();
/*aiVector2D pos=*/ReadVec2();
/*aiVector2D scale=*/ReadVec2();
/*float rot=*/ReadFloat();
_textures.push_back(name);
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadBRUS() {
int n_texs = ReadInt();
if (n_texs < 0 || n_texs > 8) {
Fail("Bad texture count");
}
while (ChunkSize()) {
string name = ReadString();
aiVector3D color = ReadVec3();
float alpha = ReadFloat();
float shiny = ReadFloat();
/*int blend=**/ ReadInt();
int fx = ReadInt();
std::unique_ptr<aiMaterial> mat(new aiMaterial);
// Name
aiString ainame(name);
mat->AddProperty(&ainame, AI_MATKEY_NAME);
// Diffuse color
mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE);
// Opacity
mat->AddProperty(&alpha, 1, AI_MATKEY_OPACITY);
// Specular color
aiColor3D speccolor(shiny, shiny, shiny);
mat->AddProperty(&speccolor, 1, AI_MATKEY_COLOR_SPECULAR);
// Specular power
float specpow = shiny * 128;
mat->AddProperty(&specpow, 1, AI_MATKEY_SHININESS);
// Double sided
if (fx & 0x10) {
int i = 1;
mat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED);
}
// Textures
for (int i = 0; i < n_texs; ++i) {
int texid = ReadInt();
if (texid < -1 || (texid >= 0 && texid >= static_cast<int>(_textures.size()))) {
Fail("Bad texture id");
}
if (i == 0 && texid >= 0) {
aiString texname(_textures[texid]);
mat->AddProperty(&texname, AI_MATKEY_TEXTURE_DIFFUSE(0));
}
}
_materials.emplace_back(std::move(mat));
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadVRTS() {
_vflags = ReadInt();
_tcsets = ReadInt();
_tcsize = ReadInt();
if (_tcsets < 0 || _tcsets > 4 || _tcsize < 0 || _tcsize > 4) {
Fail("Bad texcoord data");
}
int sz = 12 + (_vflags & 1 ? 12 : 0) + (_vflags & 2 ? 16 : 0) + (_tcsets * _tcsize * 4);
size_t n_verts = ChunkSize() / sz;
int v0 = static_cast<int>(_vertices.size());
_vertices.resize(v0 + n_verts);
for (unsigned int i = 0; i < n_verts; ++i) {
Vertex &v = _vertices[v0 + i];
memset(v.bones, 0, sizeof(v.bones));
memset(v.weights, 0, sizeof(v.weights));
v.vertex = ReadVec3();
if (_vflags & 1) {
v.normal = ReadVec3();
}
if (_vflags & 2) {
ReadQuat(); // skip v 4bytes...
}
for (int j = 0; j < _tcsets; ++j) {
float t[4] = { 0, 0, 0, 0 };
for (int k = 0; k < _tcsize; ++k) {
t[k] = ReadFloat();
}
t[1] = 1 - t[1];
if (!j) {
v.texcoords = aiVector3D(t[0], t[1], t[2]);
}
}
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadTRIS(int v0) {
int matid = ReadInt();
if (matid == -1) {
matid = 0;
} else if (matid < 0 || matid >= (int)_materials.size()) {
#ifdef DEBUG_B3D
ASSIMP_LOG_ERROR("material id=", matid);
#endif
Fail("Bad material id");
}
std::unique_ptr<aiMesh> mesh(new aiMesh);
mesh->mMaterialIndex = matid;
mesh->mNumFaces = 0;
mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
size_t n_tris = ChunkSize() / 12;
aiFace *face = mesh->mFaces = new aiFace[n_tris];
for (unsigned int i = 0; i < n_tris; ++i) {
int i0 = ReadInt() + v0;
int i1 = ReadInt() + v0;
int i2 = ReadInt() + v0;
if (i0 < 0 || i0 >= (int)_vertices.size() || i1 < 0 || i1 >= (int)_vertices.size() || i2 < 0 || i2 >= (int)_vertices.size()) {
#ifdef DEBUG_B3D
ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
#endif
Fail("Bad triangle index");
}
face->mNumIndices = 3;
face->mIndices = new unsigned[3];
face->mIndices[0] = i0;
face->mIndices[1] = i1;
face->mIndices[2] = i2;
++mesh->mNumFaces;
++face;
}
_meshes.emplace_back(std::move(mesh));
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadMESH() {
/*int matid=*/ReadInt();
int v0 = static_cast<int>(_vertices.size());
while (ChunkSize()) {
string t = ReadChunk();
if (t == "VRTS") {
ReadVRTS();
} else if (t == "TRIS") {
ReadTRIS(v0);
}
ExitChunk();
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadBONE(int id) {
while (ChunkSize()) {
int vertex = ReadInt();
float weight = ReadFloat();
if (vertex < 0 || vertex >= (int)_vertices.size()) {
Fail("Bad vertex index");
}
Vertex &v = _vertices[vertex];
for (int i = 0; i < 4; ++i) {
if (!v.weights[i]) {
v.bones[i] = static_cast<unsigned char>(id);
v.weights[i] = weight;
break;
}
}
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadKEYS(aiNodeAnim *nodeAnim) {
vector<aiVectorKey> trans, scale;
vector<aiQuatKey> rot;
int flags = ReadInt();
while (ChunkSize()) {
int frame = ReadInt();
if (flags & 1) {
trans.emplace_back(frame, ReadVec3());
}
if (flags & 2) {
scale.emplace_back(frame, ReadVec3());
}
if (flags & 4) {
rot.emplace_back(frame, ReadQuat());
}
}
if (flags & 1) {
nodeAnim->mNumPositionKeys = static_cast<unsigned int>(trans.size());
nodeAnim->mPositionKeys = to_array(trans);
}
if (flags & 2) {
nodeAnim->mNumScalingKeys = static_cast<unsigned int>(scale.size());
nodeAnim->mScalingKeys = to_array(scale);
}
if (flags & 4) {
nodeAnim->mNumRotationKeys = static_cast<unsigned int>(rot.size());
nodeAnim->mRotationKeys = to_array(rot);
}
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadANIM() {
/*int flags=*/ReadInt();
int frames = ReadInt();
float fps = ReadFloat();
std::unique_ptr<aiAnimation> anim(new aiAnimation);
anim->mDuration = frames;
anim->mTicksPerSecond = fps;
_animations.emplace_back(std::move(anim));
}
// ------------------------------------------------------------------------------------------------
aiNode *B3DImporter::ReadNODE(aiNode *parent) {
string name = ReadString();
aiVector3D t = ReadVec3();
aiVector3D s = ReadVec3();
aiQuaternion r = ReadQuat();
aiMatrix4x4 trans, scale, rot;
aiMatrix4x4::Translation(t, trans);
aiMatrix4x4::Scaling(s, scale);
rot = aiMatrix4x4(r.GetMatrix());
aiMatrix4x4 tform = trans * rot * scale;
int nodeid = static_cast<int>(_nodes.size());
aiNode *node = new aiNode(name);
_nodes.push_back(node);
node->mParent = parent;
node->mTransformation = tform;
std::unique_ptr<aiNodeAnim> nodeAnim;
vector<unsigned> meshes;
vector<aiNode *> children;
while (ChunkSize()) {
const string chunk = ReadChunk();
if (chunk == "MESH") {
unsigned int n = static_cast<unsigned int>(_meshes.size());
ReadMESH();
for (unsigned int i = n; i < static_cast<unsigned int>(_meshes.size()); ++i) {
meshes.push_back(i);
}
} else if (chunk == "BONE") {
ReadBONE(nodeid);
} else if (chunk == "ANIM") {
ReadANIM();
} else if (chunk == "KEYS") {
if (!nodeAnim) {
nodeAnim.reset(new aiNodeAnim);
nodeAnim->mNodeName = node->mName;
}
ReadKEYS(nodeAnim.get());
} else if (chunk == "NODE") {
aiNode *child = ReadNODE(node);
children.push_back(child);
}
ExitChunk();
}
if (nodeAnim) {
_nodeAnims.emplace_back(std::move(nodeAnim));
}
node->mNumMeshes = static_cast<unsigned int>(meshes.size());
node->mMeshes = to_array(meshes);
node->mNumChildren = static_cast<unsigned int>(children.size());
node->mChildren = to_array(children);
return node;
}
// ------------------------------------------------------------------------------------------------
void B3DImporter::ReadBB3D(aiScene *scene) {
_textures.clear();
_materials.clear();
_vertices.clear();
_meshes.clear();
DeleteAllBarePointers(_nodes);
_nodes.clear();
_nodeAnims.clear();
_animations.clear();
string t = ReadChunk();
if (t == "BB3D") {
int version = ReadInt();
if (!DefaultLogger::isNullLogger()) {
char dmp[128];
ai_snprintf(dmp, 128, "B3D file format version: %i", version);
ASSIMP_LOG_INFO(dmp);
}
while (ChunkSize()) {
const string chunk = ReadChunk();
if (chunk == "TEXS") {
ReadTEXS();
} else if (chunk == "BRUS") {
ReadBRUS();
} else if (chunk == "NODE") {
ReadNODE(nullptr);
}
ExitChunk();
}
}
ExitChunk();
if (!_nodes.size()) {
Fail("No nodes");
}
if (!_meshes.size()) {
Fail("No meshes");
}
// Fix nodes/meshes/bones
for (size_t i = 0; i < _nodes.size(); ++i) {
aiNode *node = _nodes[i];
for (size_t j = 0; j < node->mNumMeshes; ++j) {
aiMesh *mesh = _meshes[node->mMeshes[j]].get();
int n_tris = mesh->mNumFaces;
int n_verts = mesh->mNumVertices = n_tris * 3;
aiVector3D *mv = mesh->mVertices = new aiVector3D[n_verts], *mn = nullptr, *mc = nullptr;
if (_vflags & 1) {
mn = mesh->mNormals = new aiVector3D[n_verts];
}
if (_tcsets) {
mc = mesh->mTextureCoords[0] = new aiVector3D[n_verts];
}
aiFace *face = mesh->mFaces;
vector<vector<aiVertexWeight>> vweights(_nodes.size());
for (int vertIdx = 0; vertIdx < n_verts; vertIdx += 3) {
for (int faceIndex = 0; faceIndex < 3; ++faceIndex) {
Vertex &v = _vertices[face->mIndices[faceIndex]];
*mv++ = v.vertex;
if (mn) *mn++ = v.normal;
if (mc) *mc++ = v.texcoords;
face->mIndices[faceIndex] = vertIdx + faceIndex;
for (int k = 0; k < 4; ++k) {
if (!v.weights[k])
break;
int bone = v.bones[k];
float weight = v.weights[k];
vweights[bone].emplace_back(vertIdx + faceIndex, weight);
}
}
++face;
}
vector<aiBone *> bones;
for (size_t weightIndx = 0; weightIndx < vweights.size(); ++weightIndx) {
vector<aiVertexWeight> &weights = vweights[weightIndx];
if (!weights.size()) {
continue;
}
aiBone *bone = new aiBone;
bones.push_back(bone);
aiNode *bnode = _nodes[weightIndx];
bone->mName = bnode->mName;
bone->mNumWeights = static_cast<unsigned int>(weights.size());
bone->mWeights = to_array(weights);
aiMatrix4x4 mat = bnode->mTransformation;
while (bnode->mParent) {
bnode = bnode->mParent;
mat = bnode->mTransformation * mat;
}
bone->mOffsetMatrix = mat.Inverse();
}
mesh->mNumBones = static_cast<unsigned int>(bones.size());
mesh->mBones = to_array(bones);
}
}
// nodes
scene->mRootNode = _nodes[0];
_nodes.clear(); // node ownership now belongs to scene
// material
if (!_materials.size()) {
_materials.emplace_back(std::unique_ptr<aiMaterial>(new aiMaterial));
}
scene->mNumMaterials = static_cast<unsigned int>(_materials.size());
scene->mMaterials = unique_to_array(_materials);
// meshes
scene->mNumMeshes = static_cast<unsigned int>(_meshes.size());
scene->mMeshes = unique_to_array(_meshes);
// animations
if (_animations.size() == 1 && _nodeAnims.size()) {
aiAnimation *anim = _animations.back().get();
anim->mNumChannels = static_cast<unsigned int>(_nodeAnims.size());
anim->mChannels = unique_to_array(_nodeAnims);
scene->mNumAnimations = static_cast<unsigned int>(_animations.size());
scene->mAnimations = unique_to_array(_animations);
}
// convert to RH
MakeLeftHandedProcess makeleft;
makeleft.Execute(scene);
FlipWindingOrderProcess flip;
flip.Execute(scene);
}
} // namespace Assimp
#endif // !! ASSIMP_BUILD_NO_B3D_IMPORTER

View File

@@ -0,0 +1,132 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/**
* @file Definition of the .b3d importer class.
*/
#pragma once
#ifndef AI_B3DIMPORTER_H_INC
#define AI_B3DIMPORTER_H_INC
#include <assimp/types.h>
#include <assimp/mesh.h>
#include <assimp/material.h>
#include <assimp/BaseImporter.h>
#include <memory>
#include <vector>
struct aiNodeAnim;
struct aiNode;
struct aiAnimation;
namespace Assimp{
class B3DImporter final : public BaseImporter{
public:
B3DImporter() = default;
~B3DImporter() override;
bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override;
protected:
const aiImporterDesc* GetInfo () const override;
void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override;
private:
int ReadByte();
int ReadInt();
float ReadFloat();
aiVector2D ReadVec2();
aiVector3D ReadVec3();
aiQuaternion ReadQuat();
std::string ReadString();
std::string ReadChunk();
void ExitChunk();
size_t ChunkSize();
template<class T>
T *to_array( const std::vector<T> &v );
struct Vertex{
aiVector3D vertex;
aiVector3D normal;
aiVector3D texcoords;
unsigned char bones[4];
float weights[4];
};
AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX;
AI_WONT_RETURN void Fail(const std::string &str) AI_WONT_RETURN_SUFFIX;
void ReadTEXS();
void ReadBRUS();
void ReadVRTS();
void ReadTRIS( int v0 );
void ReadMESH();
void ReadBONE( int id );
void ReadKEYS( aiNodeAnim *nodeAnim );
void ReadANIM();
aiNode *ReadNODE( aiNode *parent );
void ReadBB3D( aiScene *scene );
size_t _pos;
std::vector<unsigned char> _buf;
std::vector<size_t> _stack;
std::vector<std::string> _textures;
std::vector<std::unique_ptr<aiMaterial> > _materials;
int _vflags,_tcsets,_tcsize;
std::vector<Vertex> _vertices;
std::vector<aiNode*> _nodes;
std::vector<std::unique_ptr<aiMesh> > _meshes;
std::vector<std::unique_ptr<aiNodeAnim> > _nodeAnims;
std::vector<std::unique_ptr<aiAnimation> > _animations;
};
}
#endif

View File

@@ -0,0 +1,524 @@
/** Implementation of the BVH loader */
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_BVH_IMPORTER
#include "BVHLoader.h"
#include <assimp/SkeletonMeshBuilder.h>
#include <assimp/TinyFormatter.h>
#include <assimp/fast_atof.h>
#include <assimp/importerdesc.h>
#include <assimp/scene.h>
#include <assimp/IOSystem.hpp>
#include <assimp/Importer.hpp>
#include <map>
#include <memory>
namespace Assimp {
using namespace Assimp::Formatter;
static constexpr aiImporterDesc desc = {
"BVH Importer (MoCap)",
"",
"",
"",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"bvh"
};
// ------------------------------------------------------------------------------------------------
// Aborts the file reading with an exception
template <typename... T>
AI_WONT_RETURN void BVHLoader::ThrowException(T &&...args) {
throw DeadlyImportError(mFileName, ":", mLine, " - ", args...);
}
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
BVHLoader::BVHLoader() :
mLine(),
mAnimTickDuration(),
mAnimNumFrames(),
noSkeletonMesh() {
// empty
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool BVHLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
static const char *tokens[] = { "HIERARCHY" };
return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
}
// ------------------------------------------------------------------------------------------------
void BVHLoader::SetupProperties(const Importer *pImp) {
noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
}
// ------------------------------------------------------------------------------------------------
// Loader meta information
const aiImporterDesc *BVHLoader::GetInfo() const {
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void BVHLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
mFileName = pFile;
// read file into memory
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
if (file == nullptr) {
throw DeadlyImportError("Failed to open file ", pFile, ".");
}
size_t fileSize = file->FileSize();
if (fileSize == 0) {
throw DeadlyImportError("File is too small.");
}
mBuffer.resize(fileSize);
file->Read(&mBuffer.front(), 1, fileSize);
// start reading
mReader = mBuffer.begin();
mLine = 1;
ReadStructure(pScene);
if (!noSkeletonMesh) {
// build a dummy mesh for the skeleton so that we see something at least
SkeletonMeshBuilder meshBuilder(pScene);
}
// construct an animation from all the motion data we read
CreateAnimation(pScene);
}
// ------------------------------------------------------------------------------------------------
// Reads the file
void BVHLoader::ReadStructure(aiScene *pScene) {
// first comes hierarchy
std::string header = GetNextToken();
if (header != "HIERARCHY")
ThrowException("Expected header string \"HIERARCHY\".");
ReadHierarchy(pScene);
// then comes the motion data
std::string motion = GetNextToken();
if (motion != "MOTION")
ThrowException("Expected beginning of motion data \"MOTION\".");
ReadMotion(pScene);
}
// ------------------------------------------------------------------------------------------------
// Reads the hierarchy
void BVHLoader::ReadHierarchy(aiScene *pScene) {
std::string root = GetNextToken();
if (root != "ROOT")
ThrowException("Expected root node \"ROOT\".");
// Go read the hierarchy from here
pScene->mRootNode = ReadNode();
}
// ------------------------------------------------------------------------------------------------
// Reads a node and recursively its children and returns the created node;
aiNode *BVHLoader::ReadNode() {
// first token is name
std::string nodeName = GetNextToken();
if (nodeName.empty() || nodeName == "{")
ThrowException("Expected node name, but found \"", nodeName, "\".");
// then an opening brace should follow
std::string openBrace = GetNextToken();
if (openBrace != "{")
ThrowException("Expected opening brace \"{\", but found \"", openBrace, "\".");
// Create a node
aiNode *node = new aiNode(nodeName);
std::vector<aiNode *> childNodes;
// and create an bone entry for it
mNodes.emplace_back(node);
Node &internNode = mNodes.back();
// now read the node's contents
std::string siteToken;
while (true) {
std::string token = GetNextToken();
// node offset to parent node
if (token == "OFFSET")
ReadNodeOffset(node);
else if (token == "CHANNELS")
ReadNodeChannels(internNode);
else if (token == "JOINT") {
// child node follows
aiNode *child = ReadNode();
child->mParent = node;
childNodes.push_back(child);
} else if (token == "End") {
// The real symbol is "End Site". Second part comes in a separate token
siteToken.clear();
siteToken = GetNextToken();
if (siteToken != "Site")
ThrowException("Expected \"End Site\" keyword, but found \"", token, " ", siteToken, "\".");
aiNode *child = ReadEndSite(nodeName);
child->mParent = node;
childNodes.push_back(child);
} else if (token == "}") {
// we're done with that part of the hierarchy
break;
} else {
// everything else is a parse error
ThrowException("Unknown keyword \"", token, "\".");
}
}
// add the child nodes if there are any
if (childNodes.size() > 0) {
node->mNumChildren = static_cast<unsigned int>(childNodes.size());
node->mChildren = new aiNode *[node->mNumChildren];
std::copy(childNodes.begin(), childNodes.end(), node->mChildren);
}
// and return the sub-hierarchy we built here
return node;
}
// ------------------------------------------------------------------------------------------------
// Reads an end node and returns the created node.
aiNode *BVHLoader::ReadEndSite(const std::string &pParentName) {
// check opening brace
std::string openBrace = GetNextToken();
if (openBrace != "{")
ThrowException("Expected opening brace \"{\", but found \"", openBrace, "\".");
// Create a node
aiNode *node = new aiNode("EndSite_" + pParentName);
// now read the node's contents. Only possible entry is "OFFSET"
std::string token;
while (true) {
token.clear();
token = GetNextToken();
// end node's offset
if (token == "OFFSET") {
ReadNodeOffset(node);
} else if (token == "}") {
// we're done with the end node
break;
} else {
// everything else is a parse error
ThrowException("Unknown keyword \"", token, "\".");
}
}
// and return the sub-hierarchy we built here
return node;
}
// ------------------------------------------------------------------------------------------------
// Reads a node offset for the given node
void BVHLoader::ReadNodeOffset(aiNode *pNode) {
// Offset consists of three floats to read
aiVector3D offset;
offset.x = GetNextTokenAsFloat();
offset.y = GetNextTokenAsFloat();
offset.z = GetNextTokenAsFloat();
// build a transformation matrix from it
pNode->mTransformation = aiMatrix4x4(1.0f, 0.0f, 0.0f, offset.x,
0.0f, 1.0f, 0.0f, offset.y,
0.0f, 0.0f, 1.0f, offset.z,
0.0f, 0.0f, 0.0f, 1.0f);
}
// ------------------------------------------------------------------------------------------------
// Reads the animation channels for the given node
void BVHLoader::ReadNodeChannels(BVHLoader::Node &pNode) {
// number of channels. Use the float reader because we're lazy
float numChannelsFloat = GetNextTokenAsFloat();
unsigned int numChannels = (unsigned int)numChannelsFloat;
for (unsigned int a = 0; a < numChannels; a++) {
std::string channelToken = GetNextToken();
if (channelToken == "Xposition")
pNode.mChannels.push_back(Channel_PositionX);
else if (channelToken == "Yposition")
pNode.mChannels.push_back(Channel_PositionY);
else if (channelToken == "Zposition")
pNode.mChannels.push_back(Channel_PositionZ);
else if (channelToken == "Xrotation")
pNode.mChannels.push_back(Channel_RotationX);
else if (channelToken == "Yrotation")
pNode.mChannels.push_back(Channel_RotationY);
else if (channelToken == "Zrotation")
pNode.mChannels.push_back(Channel_RotationZ);
else
ThrowException("Invalid channel specifier \"", channelToken, "\".");
}
}
// ------------------------------------------------------------------------------------------------
// Reads the motion data
void BVHLoader::ReadMotion(aiScene * /*pScene*/) {
// Read number of frames
std::string tokenFrames = GetNextToken();
if (tokenFrames != "Frames:")
ThrowException("Expected frame count \"Frames:\", but found \"", tokenFrames, "\".");
float numFramesFloat = GetNextTokenAsFloat();
mAnimNumFrames = (unsigned int)numFramesFloat;
// Read frame duration
std::string tokenDuration1 = GetNextToken();
std::string tokenDuration2 = GetNextToken();
if (tokenDuration1 != "Frame" || tokenDuration2 != "Time:")
ThrowException("Expected frame duration \"Frame Time:\", but found \"", tokenDuration1, " ", tokenDuration2, "\".");
mAnimTickDuration = GetNextTokenAsFloat();
// resize value vectors for each node
for (std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
it->mChannelValues.reserve(it->mChannels.size() * mAnimNumFrames);
// now read all the data and store it in the corresponding node's value vector
for (unsigned int frame = 0; frame < mAnimNumFrames; ++frame) {
// on each line read the values for all nodes
for (std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it) {
// get as many values as the node has channels
for (unsigned int c = 0; c < it->mChannels.size(); ++c)
it->mChannelValues.push_back(GetNextTokenAsFloat());
}
// after one frame worth of values for all nodes there should be a newline, but we better don't rely on it
}
}
// ------------------------------------------------------------------------------------------------
// Retrieves the next token
std::string BVHLoader::GetNextToken() {
// skip any preceding whitespace
while (mReader != mBuffer.end()) {
if (!isspace((unsigned char)*mReader))
break;
// count lines
if (*mReader == '\n')
mLine++;
++mReader;
}
// collect all chars till the next whitespace. BVH is easy in respect to that.
std::string token;
while (mReader != mBuffer.end()) {
if (isspace((unsigned char)*mReader))
break;
token.push_back(*mReader);
++mReader;
// little extra logic to make sure braces are counted correctly
if (token == "{" || token == "}")
break;
}
// empty token means end of file, which is just fine
return token;
}
// ------------------------------------------------------------------------------------------------
// Reads the next token as a float
float BVHLoader::GetNextTokenAsFloat() {
std::string token = GetNextToken();
if (token.empty())
ThrowException("Unexpected end of file while trying to read a float");
// check if the float is valid by testing if the atof() function consumed every char of the token
const char *ctoken = token.c_str();
float result = 0.0f;
ctoken = fast_atoreal_move(ctoken, result);
if (ctoken != token.c_str() + token.length())
ThrowException("Expected a floating point number, but found \"", token, "\".");
return result;
}
// ------------------------------------------------------------------------------------------------
// Constructs an animation for the motion data and stores it in the given scene
void BVHLoader::CreateAnimation(aiScene *pScene) {
// create the animation
pScene->mNumAnimations = 1;
pScene->mAnimations = new aiAnimation *[1];
aiAnimation *anim = new aiAnimation;
pScene->mAnimations[0] = anim;
// put down the basic parameters
anim->mName.Set("Motion");
anim->mTicksPerSecond = 1.0 / double(mAnimTickDuration);
anim->mDuration = double(mAnimNumFrames - 1);
// now generate the tracks for all nodes
anim->mNumChannels = static_cast<unsigned int>(mNodes.size());
anim->mChannels = new aiNodeAnim *[anim->mNumChannels];
// FIX: set the array elements to nullptr to ensure proper deletion if an exception is thrown
for (unsigned int i = 0; i < anim->mNumChannels; ++i)
anim->mChannels[i] = nullptr;
for (unsigned int a = 0; a < anim->mNumChannels; a++) {
const Node &node = mNodes[a];
const std::string nodeName = std::string(node.mNode->mName.data);
aiNodeAnim *nodeAnim = new aiNodeAnim;
anim->mChannels[a] = nodeAnim;
nodeAnim->mNodeName.Set(nodeName);
std::map<BVHLoader::ChannelType, int> channelMap;
// Build map of channels
for (unsigned int channel = 0; channel < node.mChannels.size(); ++channel) {
channelMap[node.mChannels[channel]] = channel;
}
// translational part, if given
if (node.mChannels.size() == 6) {
nodeAnim->mNumPositionKeys = mAnimNumFrames;
nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames];
aiVectorKey *poskey = nodeAnim->mPositionKeys;
for (unsigned int fr = 0; fr < mAnimNumFrames; ++fr) {
poskey->mTime = double(fr);
// Now compute all translations
for (BVHLoader::ChannelType channel = Channel_PositionX; channel <= Channel_PositionZ; channel = (BVHLoader::ChannelType)(channel + 1)) {
// Find channel in node
std::map<BVHLoader::ChannelType, int>::iterator mapIter = channelMap.find(channel);
if (mapIter == channelMap.end())
throw DeadlyImportError("Missing position channel in node ", nodeName);
else {
int channelIdx = mapIter->second;
switch (channel) {
case Channel_PositionX:
poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channelIdx];
break;
case Channel_PositionY:
poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channelIdx];
break;
case Channel_PositionZ:
poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channelIdx];
break;
default:
break;
}
}
}
++poskey;
}
} else {
// if no translation part is given, put a default sequence
aiVector3D nodePos(node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4);
nodeAnim->mNumPositionKeys = 1;
nodeAnim->mPositionKeys = new aiVectorKey[1];
nodeAnim->mPositionKeys[0].mTime = 0.0;
nodeAnim->mPositionKeys[0].mValue = nodePos;
}
// rotation part. Always present. First find value offsets
{
// Then create the number of rotation keys
nodeAnim->mNumRotationKeys = mAnimNumFrames;
nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames];
aiQuatKey *rotkey = nodeAnim->mRotationKeys;
for (unsigned int fr = 0; fr < mAnimNumFrames; ++fr) {
aiMatrix4x4 temp;
aiMatrix3x3 rotMatrix;
for (unsigned int channelIdx = 0; channelIdx < node.mChannels.size(); ++channelIdx) {
switch (node.mChannels[channelIdx]) {
case Channel_RotationX: {
const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f;
aiMatrix4x4::RotationX(angle, temp);
rotMatrix *= aiMatrix3x3(temp);
} break;
case Channel_RotationY: {
const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f;
aiMatrix4x4::RotationY(angle, temp);
rotMatrix *= aiMatrix3x3(temp);
} break;
case Channel_RotationZ: {
const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f;
aiMatrix4x4::RotationZ(angle, temp);
rotMatrix *= aiMatrix3x3(temp);
} break;
default:
break;
}
}
rotkey->mTime = double(fr);
rotkey->mValue = aiQuaternion(rotMatrix);
++rotkey;
}
}
// scaling part. Always just a default track
{
nodeAnim->mNumScalingKeys = 1;
nodeAnim->mScalingKeys = new aiVectorKey[1];
nodeAnim->mScalingKeys[0].mTime = 0.0;
nodeAnim->mScalingKeys[0].mValue.Set(1.0f, 1.0f, 1.0f);
}
}
}
} // namespace Assimp
#endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER

View File

@@ -0,0 +1,164 @@
/** Defines the BHV motion capturing loader class */
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BVHLoader.h
* @brief Biovision BVH import
*/
#ifndef AI_BVHLOADER_H_INC
#define AI_BVHLOADER_H_INC
#include <assimp/BaseImporter.h>
struct aiNode;
namespace Assimp {
// --------------------------------------------------------------------------------
/** Loader class to read Motion Capturing data from a .bvh file.
*
* This format only contains a hierarchy of joints and a series of keyframes for
* the hierarchy. It contains no actual mesh data, but we generate a dummy mesh
* inside the loader just to be able to see something.
*/
class BVHLoader final : public BaseImporter {
/** Possible animation channels for which the motion data holds the values */
enum ChannelType {
Channel_PositionX,
Channel_PositionY,
Channel_PositionZ,
Channel_RotationX,
Channel_RotationY,
Channel_RotationZ
};
/** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */
struct Node {
const aiNode *mNode;
std::vector<ChannelType> mChannels;
std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
Node() : mNode(nullptr) {}
explicit Node(const aiNode *pNode) :mNode(pNode) {}
};
public:
BVHLoader();
~BVHLoader() override = default;
/** Returns whether the class can handle the format of the given file.
* See BaseImporter::CanRead() for details. */
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const override;
void SetupProperties(const Importer *pImp) override;
const aiImporterDesc *GetInfo() const override;
protected:
/** Imports the given file into the given scene structure.
* See BaseImporter::InternReadFile() for details
*/
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
/** Reads the file */
void ReadStructure(aiScene *pScene);
/** Reads the hierarchy */
void ReadHierarchy(aiScene *pScene);
/** Reads a node and recursively its children and returns the created node. */
aiNode *ReadNode();
/** Reads an end node and returns the created node. */
aiNode *ReadEndSite(const std::string &pParentName);
/** Reads a node offset for the given node */
void ReadNodeOffset(aiNode *pNode);
/** Reads the animation channels into the given node */
void ReadNodeChannels(BVHLoader::Node &pNode);
/** Reads the motion data */
void ReadMotion(aiScene *pScene);
/** Retrieves the next token */
std::string GetNextToken();
/** Reads the next token as a float */
float GetNextTokenAsFloat();
/** Aborts the file reading with an exception */
template<typename... T>
AI_WONT_RETURN void ThrowException(T&&... args) AI_WONT_RETURN_SUFFIX;
/** Constructs an animation for the motion data and stores it in the given scene */
void CreateAnimation(aiScene *pScene);
protected:
/** Filename, for a verbose error message */
std::string mFileName;
/** Buffer to hold the loaded file */
std::vector<char> mBuffer;
/** Next char to read from the buffer */
std::vector<char>::const_iterator mReader;
/** Current line, for error messages */
unsigned int mLine;
/** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index.
* Also contain the motion data for the node's channels
*/
std::vector<Node> mNodes;
/** basic Animation parameters */
float mAnimTickDuration;
unsigned int mAnimNumFrames;
bool noSkeletonMesh;
};
} // end of namespace Assimp
#endif // AI_BVHLOADER_H_INC

View File

@@ -0,0 +1,185 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderBMesh.cpp
* @brief Conversion of Blender's new BMesh stuff
*/
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
#include "BlenderBMesh.h"
#include "BlenderDNA.h"
#include "BlenderScene.h"
#include "BlenderTessellator.h"
namespace Assimp {
template <>
const char *LogFunctions<BlenderBMeshConverter>::Prefix() {
return "BLEND_BMESH: ";
}
} // namespace Assimp
using namespace Assimp;
using namespace Assimp::Blender;
using namespace Assimp::Formatter;
// ------------------------------------------------------------------------------------------------
BlenderBMeshConverter::BlenderBMeshConverter(const Mesh *mesh) :
BMesh(mesh),
triMesh(nullptr) {
ai_assert(nullptr != mesh);
}
// ------------------------------------------------------------------------------------------------
BlenderBMeshConverter::~BlenderBMeshConverter() {
DestroyTriMesh();
}
// ------------------------------------------------------------------------------------------------
bool BlenderBMeshConverter::ContainsBMesh() const {
if (BMesh == nullptr) {
return false;
}
return BMesh->totpoly && BMesh->totloop && BMesh->totvert;
}
// ------------------------------------------------------------------------------------------------
const Mesh *BlenderBMeshConverter::TriangulateBMesh() {
AssertValidMesh();
AssertValidSizes();
PrepareTriMesh();
for (int i = 0; i < BMesh->totpoly; ++i) {
const MPoly &poly = BMesh->mpoly[i];
ConvertPolyToFaces(poly);
}
return triMesh;
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::AssertValidMesh() {
if (!ContainsBMesh()) {
ThrowException("BlenderBMeshConverter requires a BMesh with \"polygons\" - please call BlenderBMeshConverter::ContainsBMesh to check this first");
}
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::AssertValidSizes() {
if (BMesh->totpoly != static_cast<int>(BMesh->mpoly.size())) {
ThrowException("BMesh poly array has incorrect size");
}
if (BMesh->totloop != static_cast<int>(BMesh->mloop.size())) {
ThrowException("BMesh loop array has incorrect size");
}
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::PrepareTriMesh() {
if (triMesh) {
DestroyTriMesh();
}
triMesh = new Mesh(*BMesh);
triMesh->totface = 0;
triMesh->mface.clear();
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::DestroyTriMesh() {
delete triMesh;
triMesh = nullptr;
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::ConvertPolyToFaces(const MPoly &poly) {
const MLoop *polyLoop = &BMesh->mloop[poly.loopstart];
if (poly.totloop == 3 || poly.totloop == 4) {
AddFace(polyLoop[0].v, polyLoop[1].v, polyLoop[2].v, poly.totloop == 4 ? polyLoop[3].v : 0);
// UVs are optional, so only convert when present.
if (BMesh->mloopuv.size()) {
if ((poly.loopstart + poly.totloop) > static_cast<int>(BMesh->mloopuv.size())) {
ThrowException("BMesh uv loop array has incorrect size");
}
const MLoopUV *loopUV = &BMesh->mloopuv[poly.loopstart];
AddTFace(loopUV[0].uv, loopUV[1].uv, loopUV[2].uv, poly.totloop == 4 ? loopUV[3].uv : nullptr);
}
} else if (poly.totloop > 4) {
#if ASSIMP_BLEND_WITH_GLU_TESSELLATE
BlenderTessellatorGL tessGL(*this);
tessGL.Tessellate(polyLoop, poly.totloop, triMesh->mvert);
#elif ASSIMP_BLEND_WITH_POLY_2_TRI
BlenderTessellatorP2T tessP2T(*this);
tessP2T.Tessellate(polyLoop, poly.totloop, triMesh->mvert);
#endif
}
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::AddFace(int v1, int v2, int v3, int v4) {
MFace face;
face.v1 = v1;
face.v2 = v2;
face.v3 = v3;
face.v4 = v4;
face.flag = 0;
// TODO - Work out how materials work
face.mat_nr = 0;
triMesh->mface.push_back(face);
triMesh->totface = static_cast<int>(triMesh->mface.size());
}
// ------------------------------------------------------------------------------------------------
void BlenderBMeshConverter::AddTFace(const float *uv1, const float *uv2, const float *uv3, const float *uv4) {
MTFace mtface;
memcpy(&mtface.uv[0], uv1, sizeof(float) * 2);
memcpy(&mtface.uv[1], uv2, sizeof(float) * 2);
memcpy(&mtface.uv[2], uv3, sizeof(float) * 2);
if (uv4) {
memcpy(&mtface.uv[3], uv4, sizeof(float) * 2);
}
triMesh->mtface.push_back(mtface);
}
#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

View File

@@ -0,0 +1,92 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderBMesh.h
* @brief Conversion of Blender's new BMesh stuff
*/
#ifndef INCLUDED_AI_BLEND_BMESH_H
#define INCLUDED_AI_BLEND_BMESH_H
#include <assimp/LogAux.h>
namespace Assimp
{
// TinyFormatter.h
namespace Formatter
{
template < typename T,typename TR, typename A > class basic_formatter;
typedef class basic_formatter< char, std::char_traits< char >, std::allocator< char > > format;
}
// BlenderScene.h
namespace Blender
{
struct Mesh;
struct MPoly;
struct MLoop;
}
class BlenderBMeshConverter final : public LogFunctions< BlenderBMeshConverter >
{
public:
explicit BlenderBMeshConverter( const Blender::Mesh* mesh );
~BlenderBMeshConverter();
bool ContainsBMesh() const;
const Blender::Mesh* TriangulateBMesh( );
private:
void AssertValidMesh( );
void AssertValidSizes( );
void PrepareTriMesh( );
void DestroyTriMesh( );
void ConvertPolyToFaces( const Blender::MPoly& poly );
void AddFace( int v1, int v2, int v3, int v4 = 0 );
void AddTFace(const float *uv1, const float *uv2, const float *uv3, const float *uv4 = nullptr);
const Blender::Mesh* BMesh;
Blender::Mesh* triMesh;
friend class BlenderTessellatorGL;
friend class BlenderTessellatorP2T;
};
} // end of namespace Assimp
#endif // INCLUDED_AI_BLEND_BMESH_H

View File

@@ -0,0 +1,183 @@
#include "BlenderCustomData.h"
#include "BlenderDNA.h"
#include <array>
#include <functional>
namespace Assimp {
namespace Blender {
/**
* @brief read/convert of Structure array to memory
*/
template <typename T>
bool read(const Structure &s, T *p, const size_t cnt, const FileDatabase &db) {
for (size_t i = 0; i < cnt; ++i) {
T read;
s.Convert(read, db);
*p = read;
p++;
}
return true;
}
/**
* @brief pointer to function read memory for n CustomData types
*/
typedef bool (*PRead)(ElemBase *pOut, const size_t cnt, const FileDatabase &db);
typedef ElemBase *(*PCreate)(const size_t cnt);
typedef void (*PDestroy)(ElemBase *);
#define IMPL_STRUCT_READ(ty) \
bool read##ty(ElemBase *v, const size_t cnt, const FileDatabase &db) { \
ty *ptr = dynamic_cast<ty *>(v); \
if (nullptr == ptr) { \
return false; \
} \
return read<ty>(db.dna[#ty], ptr, cnt, db); \
}
#define IMPL_STRUCT_CREATE(ty) \
ElemBase *create##ty(const size_t cnt) { \
return new ty[cnt]; \
}
#define IMPL_STRUCT_DESTROY(ty) \
void destroy##ty(ElemBase *pE) { \
ty *p = dynamic_cast<ty *>(pE); \
delete[] p; \
}
/**
* @brief helper macro to define Structure functions
*/
#define IMPL_STRUCT(ty) \
IMPL_STRUCT_READ(ty) \
IMPL_STRUCT_CREATE(ty) \
IMPL_STRUCT_DESTROY(ty)
// supported structures for CustomData
IMPL_STRUCT(MVert)
IMPL_STRUCT(MEdge)
IMPL_STRUCT(MFace)
IMPL_STRUCT(MTFace)
IMPL_STRUCT(MTexPoly)
IMPL_STRUCT(MLoopUV)
IMPL_STRUCT(MLoopCol)
IMPL_STRUCT(MPoly)
IMPL_STRUCT(MLoop)
/**
* @brief describes the size of data and the read function to be used for single CustomerData.type
*/
struct CustomDataTypeDescription {
PRead Read; ///< function to read one CustomData type element
PCreate Create; ///< function to allocate n type elements
PDestroy Destroy;
CustomDataTypeDescription(PRead read, PCreate create, PDestroy destroy) :
Read(read), Create(create), Destroy(destroy) {}
};
/**
* @brief helper macro to define Structure type specific CustomDataTypeDescription
* @note IMPL_STRUCT_READ for same ty must be used earlier to implement the typespecific read function
*/
#define DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(ty) \
CustomDataTypeDescription { &read##ty, &create##ty, &destroy##ty }
/**
* @brief helper macro to define CustomDataTypeDescription for UNSUPPORTED type
*/
#define DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION \
CustomDataTypeDescription { nullptr, nullptr, nullptr }
/**
* @brief descriptors for data pointed to from CustomDataLayer.data
* @note some of the CustomData uses already well defined Structures
* other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures
* use a special readfunction for that cases
*/
static const std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { {
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert),
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MFace),
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTFace),
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTexPoly),
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopUV),
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopCol),
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MPoly),
DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoop),
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION
} };
bool isValidCustomDataType(const int cdtype) {
return cdtype >= 0 && cdtype < CD_NUMTYPES;
}
bool readCustomData(std::shared_ptr<ElemBase> &out, const int cdtype, const size_t cnt, const FileDatabase &db) {
if (!isValidCustomDataType(cdtype)) {
throw Error("CustomData.type ", cdtype, " out of index");
}
const CustomDataTypeDescription cdtd = customDataTypeDescriptions[cdtype];
if (cdtd.Read && cdtd.Create && cdtd.Destroy && cnt > 0) {
// allocate cnt elements and parse them from file
out.reset(cdtd.Create(cnt), cdtd.Destroy);
return cdtd.Read(out.get(), cnt, db);
}
return false;
}
std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) {
for (auto it = customdata.layers.begin(); it != customdata.layers.end(); ++it) {
if (it->get()->type == cdtype && name == it->get()->name) {
return *it;
}
}
return nullptr;
}
const ElemBase *getCustomDataLayerData(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) {
const std::shared_ptr<CustomDataLayer> pLayer = getCustomDataLayer(customdata, cdtype, name);
if (pLayer && pLayer->data) {
return pLayer->data.get();
}
return nullptr;
}
} // namespace Blender
} // namespace Assimp

View File

@@ -0,0 +1,89 @@
#pragma once
#include "BlenderDNA.h"
#include "BlenderScene.h"
#include <memory>
namespace Assimp {
namespace Blender {
/* CustomData.type from Blender (2.79b) */
enum CustomDataType {
CD_AUTO_FROM_NAME = -1,
CD_MVERT = 0,
#ifdef DNA_DEPRECATED
CD_MSTICKY = 1, /* DEPRECATED */
#endif
CD_MDEFORMVERT = 2,
CD_MEDGE = 3,
CD_MFACE = 4,
CD_MTFACE = 5,
CD_MCOL = 6,
CD_ORIGINDEX = 7,
CD_NORMAL = 8,
/* CD_POLYINDEX = 9, */
CD_PROP_FLT = 10,
CD_PROP_INT = 11,
CD_PROP_STR = 12,
CD_ORIGSPACE = 13, /* for modifier stack face location mapping */
CD_ORCO = 14,
CD_MTEXPOLY = 15,
CD_MLOOPUV = 16,
CD_MLOOPCOL = 17,
CD_TANGENT = 18,
CD_MDISPS = 19,
CD_PREVIEW_MCOL = 20, /* for displaying weightpaint colors */
/* CD_ID_MCOL = 21, */
CD_TEXTURE_MLOOPCOL = 22,
CD_CLOTH_ORCO = 23,
CD_RECAST = 24,
/* BMESH ONLY START */
CD_MPOLY = 25,
CD_MLOOP = 26,
CD_SHAPE_KEYINDEX = 27,
CD_SHAPEKEY = 28,
CD_BWEIGHT = 29,
CD_CREASE = 30,
CD_ORIGSPACE_MLOOP = 31,
CD_PREVIEW_MLOOPCOL = 32,
CD_BM_ELEM_PYPTR = 33,
/* BMESH ONLY END */
CD_PAINT_MASK = 34,
CD_GRID_PAINT_MASK = 35,
CD_MVERT_SKIN = 36,
CD_FREESTYLE_EDGE = 37,
CD_FREESTYLE_FACE = 38,
CD_MLOOPTANGENT = 39,
CD_TESSLOOPNORMAL = 40,
CD_CUSTOMLOOPNORMAL = 41,
CD_NUMTYPES = 42
};
/**
* @brief check if given cdtype is valid (ie >= 0 and < CD_NUMTYPES)
* @param[in] cdtype to check
* @return true when valid
*/
bool isValidCustomDataType(const int cdtype);
/**
* @brief returns CustomDataLayer ptr for given cdtype and name
* @param[in] customdata CustomData to search for wanted layer
* @param[in] cdtype to search for
* @param[in] name to search for
* @return CustomDataLayer * or nullptr if not found
*/
std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, CustomDataType cdtype, const std::string &name);
/**
* @brief returns CustomDataLayer data ptr for given cdtype and name
* @param[in] customdata CustomData to search for wanted layer
* @param[in] cdtype to search for
* @param[in] name to search for
* @return * to struct data or nullptr if not found
*/
const ElemBase * getCustomDataLayerData(const CustomData &customdata, CustomDataType cdtype, const std::string &name);
}
}

View File

@@ -0,0 +1,350 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderDNA.cpp
* @brief Implementation of the Blender `DNA`, that is its own
* serialized set of data structures.
*/
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
#include "BlenderDNA.h"
#include <assimp/StreamReader.h>
#include <assimp/TinyFormatter.h>
#include <assimp/fast_atof.h>
using namespace Assimp;
using namespace Assimp::Blender;
using namespace Assimp::Formatter;
static bool match4(StreamReaderAny &stream, const char *string) {
ai_assert(nullptr != string);
char tmp[4];
tmp[0] = (stream).GetI1();
tmp[1] = (stream).GetI1();
tmp[2] = (stream).GetI1();
tmp[3] = (stream).GetI1();
return (tmp[0] == string[0] && tmp[1] == string[1] && tmp[2] == string[2] && tmp[3] == string[3]);
}
struct Type {
size_t size;
std::string name;
};
// ------------------------------------------------------------------------------------------------
void DNAParser::Parse() {
StreamReaderAny &stream = *db.reader;
DNA &dna = db.dna;
if (!match4(stream, "SDNA")) {
throw DeadlyImportError("BlenderDNA: Expected SDNA chunk");
}
// name dictionary
if (!match4(stream, "NAME")) {
throw DeadlyImportError("BlenderDNA: Expected NAME field");
}
std::vector<std::string> names(stream.GetI4());
for (std::string &s : names) {
while (char c = stream.GetI1()) {
s += c;
}
}
// type dictionary
for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
;
if (!match4(stream, "TYPE")) {
throw DeadlyImportError("BlenderDNA: Expected TYPE field");
}
std::vector<Type> types(stream.GetI4());
for (Type &s : types) {
while (char c = stream.GetI1()) {
s.name += c;
}
}
// type length dictionary
for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
;
if (!match4(stream, "TLEN")) {
throw DeadlyImportError("BlenderDNA: Expected TLEN field");
}
for (Type &s : types) {
s.size = stream.GetI2();
}
// structures dictionary
for (; stream.GetCurrentPos() & 0x3; stream.GetI1())
;
if (!match4(stream, "STRC")) {
throw DeadlyImportError("BlenderDNA: Expected STRC field");
}
size_t end = stream.GetI4(), fields = 0;
dna.structures.reserve(end);
for (size_t i = 0; i != end; ++i) {
uint16_t n = stream.GetI2();
if (n >= types.size()) {
throw DeadlyImportError("BlenderDNA: Invalid type index in structure name", n, " (there are only ", types.size(), " entries)");
}
// maintain separate indexes
dna.indices[types[n].name] = dna.structures.size();
dna.structures.push_back(Structure());
Structure &s = dna.structures.back();
s.name = types[n].name;
n = stream.GetI2();
s.fields.reserve(n);
size_t offset = 0;
for (size_t m = 0; m < n; ++m, ++fields) {
uint16_t j = stream.GetI2();
if (j >= types.size()) {
throw DeadlyImportError("BlenderDNA: Invalid type index in structure field ", j, " (there are only ", types.size(), " entries)");
}
s.fields.push_back(Field());
Field &f = s.fields.back();
f.offset = offset;
f.type = types[j].name;
f.size = types[j].size;
j = stream.GetI2();
if (j >= names.size()) {
throw DeadlyImportError("BlenderDNA: Invalid name index in structure field ", j, " (there are only ", names.size(), " entries)");
}
f.name = names[j];
f.flags = 0u;
// pointers always specify the size of the pointee instead of their own.
// The pointer asterisk remains a property of the lookup name.
if (f.name[0] == '*') {
f.size = db.i64bit ? 8 : 4;
f.flags |= FieldFlag_Pointer;
}
// arrays, however, specify the size of a single element so we
// need to parse the (possibly multi-dimensional) array declaration
// in order to obtain the actual size of the array in the file.
// Also we need to alter the lookup name to include no array
// brackets anymore or size fixup won't work (if our size does
// not match the size read from the DNA).
if (*f.name.rbegin() == ']') {
const std::string::size_type rb = f.name.find('[');
if (rb == std::string::npos) {
throw DeadlyImportError("BlenderDNA: Encountered invalid array declaration ", f.name);
}
f.flags |= FieldFlag_Array;
DNA::ExtractArraySize(f.name, f.array_sizes);
f.name = f.name.substr(0, rb);
f.size *= f.array_sizes[0] * f.array_sizes[1];
}
// maintain separate indexes
s.indices[f.name] = s.fields.size() - 1;
offset += f.size;
}
s.size = offset;
}
ASSIMP_LOG_DEBUG("BlenderDNA: Got ", dna.structures.size(), " structures with totally ", fields, " fields");
#if ASSIMP_BUILD_BLENDER_DEBUG_DNA
dna.DumpToFile();
#endif
dna.AddPrimitiveStructures();
dna.RegisterConverters();
}
#if ASSIMP_BUILD_BLENDER_DEBUG_DNA
#include <fstream>
// ------------------------------------------------------------------------------------------------
void DNA ::DumpToFile() {
// we don't bother using the VFS here for this is only for debugging.
// (and all your bases are belong to us).
std::ofstream f("dna.txt");
if (f.fail()) {
ASSIMP_LOG_ERROR("Could not dump dna to dna.txt");
return;
}
f << "Field format: type name offset size"
<< "\n";
f << "Structure format: name size"
<< "\n";
for (const Structure &s : structures) {
f << s.name << " " << s.size << "\n\n";
for (const Field &ff : s.fields) {
f << "\t" << ff.type << " " << ff.name << " " << ff.offset << " " << ff.size << "\n";
}
f << "\n";
}
f << std::flush;
ASSIMP_LOG_INFO("BlenderDNA: Dumped dna to dna.txt");
}
#endif // ASSIMP_BUILD_BLENDER_DEBUG_DNA
// ------------------------------------------------------------------------------------------------
/*static*/ void DNA ::ExtractArraySize(
const std::string &out,
size_t array_sizes[2]) {
array_sizes[0] = array_sizes[1] = 1;
std::string::size_type pos = out.find('[');
if (pos++ == std::string::npos) {
return;
}
array_sizes[0] = strtoul10(&out[pos]);
pos = out.find('[', pos);
if (pos++ == std::string::npos) {
return;
}
array_sizes[1] = strtoul10(&out[pos]);
}
// ------------------------------------------------------------------------------------------------
std::shared_ptr<ElemBase> DNA ::ConvertBlobToStructure(
const Structure &structure,
const FileDatabase &db) const {
std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name);
if (it == converters.end()) {
return std::shared_ptr<ElemBase>();
}
std::shared_ptr<ElemBase> ret = (structure.*((*it).second.first))();
(structure.*((*it).second.second))(ret, db);
return ret;
}
// ------------------------------------------------------------------------------------------------
DNA::FactoryPair DNA ::GetBlobToStructureConverter(
const Structure &structure,
const FileDatabase & /*db*/
) const {
std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name);
return it == converters.end() ? FactoryPair() : (*it).second;
}
// basing on http://www.blender.org/development/architecture/notes-on-sdna/
// ------------------------------------------------------------------------------------------------
void DNA ::AddPrimitiveStructures() {
// NOTE: these are just dummies. Their presence enforces
// Structure::Convert<target_type> to be called on these
// empty structures. These converters are special
// overloads which scan the name of the structure and
// perform the required data type conversion if one
// of these special names is found in the structure
// in question.
indices["int"] = structures.size();
structures.push_back(Structure());
structures.back().name = "int";
structures.back().size = 4;
indices["short"] = structures.size();
structures.push_back(Structure());
structures.back().name = "short";
structures.back().size = 2;
indices["char"] = structures.size();
structures.push_back(Structure());
structures.back().name = "char";
structures.back().size = 1;
indices["float"] = structures.size();
structures.push_back(Structure());
structures.back().name = "float";
structures.back().size = 4;
indices["double"] = structures.size();
structures.push_back(Structure());
structures.back().name = "double";
structures.back().size = 8;
// no long, seemingly.
}
// ------------------------------------------------------------------------------------------------
void SectionParser ::Next() {
stream.SetCurrentPos(current.start + current.size);
const char tmp[] = {
(char)stream.GetI1(),
(char)stream.GetI1(),
(char)stream.GetI1(),
(char)stream.GetI1()
};
current.id = std::string(tmp, tmp[3] ? 4 : tmp[2] ? 3 : tmp[1] ? 2 : 1);
current.size = stream.GetI4();
current.address.val = ptr64 ? stream.GetU8() : stream.GetU4();
current.dna_index = stream.GetI4();
current.num = stream.GetI4();
current.start = stream.GetCurrentPos();
if (stream.GetRemainingSizeToLimit() < current.size) {
throw DeadlyImportError("BLEND: invalid size of file block");
}
#ifdef ASSIMP_BUILD_BLENDER_DEBUG
ASSIMP_LOG_VERBOSE_DEBUG(current.id);
#endif
}
#endif

View File

@@ -0,0 +1,791 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderDNA.h
* @brief Blender `DNA` (file format specification embedded in
* blend file itself) loader.
*/
#ifndef INCLUDED_AI_BLEND_DNA_H
#define INCLUDED_AI_BLEND_DNA_H
#include <assimp/BaseImporter.h>
#include <assimp/StreamReader.h>
#include <stdint.h>
#include <assimp/DefaultLogger.hpp>
#include <map>
#include <memory>
// enable verbose log output. really verbose, so be careful.
#ifdef ASSIMP_BUILD_DEBUG
# define ASSIMP_BUILD_BLENDER_DEBUG
#endif
// set this to non-zero to dump BlenderDNA stuff to dna.txt.
// you could set it on the assimp build command line too without touching it here.
// !!! please make sure this is set to 0 in the repo !!!
#ifndef ASSIMP_BUILD_BLENDER_DEBUG_DNA
# define ASSIMP_BUILD_BLENDER_DEBUG_DNA 0
#endif
// #define ASSIMP_BUILD_BLENDER_NO_STATS
namespace Assimp {
template <bool, bool>
class StreamReader;
typedef StreamReader<true, true> StreamReaderAny;
namespace Blender {
class FileDatabase;
struct FileBlockHead;
template <template <typename> class TOUT>
class ObjectCache;
// -------------------------------------------------------------------------------
/** Exception class used by the blender loader to selectively catch exceptions
* thrown in its own code (DeadlyImportErrors thrown in general utility
* functions are untouched then). If such an exception is not caught by
* the loader itself, it will still be caught by Assimp due to its
* ancestry. */
// -------------------------------------------------------------------------------
struct Error : DeadlyImportError {
template <typename... T>
explicit Error(T &&...args) :
DeadlyImportError(args...) {
}
};
// -------------------------------------------------------------------------------
/** The only purpose of this structure is to feed a virtual dtor into its
* descendents. It serves as base class for all data structure fields. */
// -------------------------------------------------------------------------------
struct ElemBase {
ElemBase() :
dna_type(nullptr) {
// empty
}
virtual ~ElemBase() = default;
/** Type name of the element. The type
* string points is the `c_str` of the `name` attribute of the
* corresponding `Structure`, that is, it is only valid as long
* as the DNA is not modified. The dna_type is only set if the
* data type is not static, i.e. a std::shared_ptr<ElemBase>
* in the scene description would have its type resolved
* at runtime, so this member is always set. */
const char *dna_type;
};
// -------------------------------------------------------------------------------
/** Represents a generic pointer to a memory location, which can be either 32
* or 64 bits. These pointers are loaded from the BLEND file and finally
* fixed to point to the real, converted representation of the objects
* they used to point to.*/
// -------------------------------------------------------------------------------
struct Pointer {
uint64_t val{0};
};
// -------------------------------------------------------------------------------
/** Represents a generic offset within a BLEND file */
// -------------------------------------------------------------------------------
struct FileOffset {
uint64_t val{0};
};
// -------------------------------------------------------------------------------
/** Dummy derivate of std::vector to be able to use it in templates simultaenously
* with std::shared_ptr, which takes only one template argument
* while std::vector takes three. Also we need to provide some special member
* functions of shared_ptr */
// -------------------------------------------------------------------------------
template <typename T>
class vector : public std::vector<T> {
public:
using std::vector<T>::resize;
using std::vector<T>::empty;
void reset() {
resize(0);
}
operator bool() const {
return !empty();
}
};
// -------------------------------------------------------------------------------
/** Mixed flags for use in #Field */
// -------------------------------------------------------------------------------
enum FieldFlags {
FieldFlag_Pointer = 0x1,
FieldFlag_Array = 0x2
};
// -------------------------------------------------------------------------------
/** Represents a single member of a data structure in a BLEND file */
// -------------------------------------------------------------------------------
struct Field {
std::string name;
std::string type;
size_t size;
size_t offset;
/** Size of each array dimension. For flat arrays,
* the second dimension is set to 1. */
size_t array_sizes[2];
/** Any of the #FieldFlags enumerated values */
unsigned int flags;
};
// -------------------------------------------------------------------------------
/** Range of possible behaviors for fields absence in the input file. Some are
* mission critical so we need them, while others can silently be default
* initialized and no animations are harmed. */
// -------------------------------------------------------------------------------
enum ErrorPolicy {
/** Substitute default value and ignore */
ErrorPolicy_Igno,
/** Substitute default value and write to log */
ErrorPolicy_Warn,
/** Substitute a massive error message and crash the whole matrix. Its time for another zion */
ErrorPolicy_Fail
};
#ifdef ASSIMP_BUILD_BLENDER_DEBUG
# define ErrorPolicy_Igno ErrorPolicy_Warn
#endif
// -------------------------------------------------------------------------------
/** Represents a data structure in a BLEND file. A Structure defines n fields
* and their locations and encodings the input stream. Usually, every
* Structure instance pertains to one equally-named data structure in the
* BlenderScene.h header. This class defines various utilities to map a
* binary `blob` read from the file to such a structure instance with
* meaningful contents. */
// -------------------------------------------------------------------------------
class Structure {
template <template <typename> class>
friend class ObjectCache;
public:
Structure() :
cache_idx(static_cast<size_t>(-1)) {
// empty
}
// publicly accessible members
std::string name;
vector<Field> fields;
std::map<std::string, size_t> indices;
size_t size;
// --------------------------------------------------------
/** Access a field of the structure by its canonical name. The pointer version
* returns nullptr on failure while the reference version raises an import error. */
inline const Field &operator[](const std::string &ss) const;
inline const Field *Get(const std::string &ss) const;
// --------------------------------------------------------
/** Access a field of the structure by its index */
inline const Field &operator[](const size_t i) const;
// --------------------------------------------------------
inline bool operator==(const Structure &other) const {
return name == other.name; // name is meant to be an unique identifier
}
// --------------------------------------------------------
inline bool operator!=(const Structure &other) const {
return name != other.name;
}
// --------------------------------------------------------
/** Try to read an instance of the structure from the stream
* and attempt to convert to `T`. This is done by
* an appropriate specialization. If none is available,
* a compiler complain is the result.
* @param dest Destination value to be written
* @param db File database, including input stream. */
template <typename T>
void Convert(T &dest, const FileDatabase &db) const;
// --------------------------------------------------------
// generic converter
template <typename T>
void Convert(std::shared_ptr<ElemBase> in, const FileDatabase &db) const;
// --------------------------------------------------------
// generic allocator
template <typename T>
std::shared_ptr<ElemBase> Allocate() const;
// --------------------------------------------------------
// field parsing for 1d arrays
template <int error_policy, typename T, size_t M>
void ReadFieldArray(T (&out)[M], const char *name,
const FileDatabase &db) const;
// --------------------------------------------------------
// field parsing for 2d arrays
template <int error_policy, typename T, size_t M, size_t N>
void ReadFieldArray2(T (&out)[M][N], const char *name,
const FileDatabase &db) const;
// --------------------------------------------------------
// field parsing for pointer or dynamic array types
// (std::shared_ptr)
// The return value indicates whether the data was already cached.
template <int error_policy, template <typename> class TOUT, typename T>
bool ReadFieldPtr(TOUT<T> &out, const char *name,
const FileDatabase &db,
bool non_recursive = false) const;
// --------------------------------------------------------
// field parsing for static arrays of pointer or dynamic
// array types (std::shared_ptr[])
// The return value indicates whether the data was already cached.
template <int error_policy, template <typename> class TOUT, typename T, size_t N>
bool ReadFieldPtr(TOUT<T> (&out)[N], const char *name,
const FileDatabase &db) const;
// --------------------------------------------------------
// field parsing for `normal` values
// The return value indicates whether the data was already cached.
template <int error_policy, typename T>
void ReadField(T &out, const char *name,
const FileDatabase &db) const;
// --------------------------------------------------------
/**
* @brief field parsing for dynamic vectors
* @param[in] out vector of struct to be filled
* @param[in] name of field
* @param[in] db to access the file, dna, ...
* @return true when read was successful
*/
template <int error_policy, template <typename> class TOUT, typename T>
bool ReadFieldPtrVector(vector<TOUT<T>> &out, const char *name, const FileDatabase &db) const;
/**
* @brief parses raw customdata
* @param[in] out shared_ptr to be filled
* @param[in] cdtype customdata type to read
* @param[in] name of field ptr
* @param[in] db to access the file, dna, ...
* @return true when read was successful
*/
template <int error_policy>
bool ReadCustomDataPtr(std::shared_ptr<ElemBase> &out, int cdtype, const char *name, const FileDatabase &db) const;
private:
// --------------------------------------------------------
template <template <typename> class TOUT, typename T>
bool ResolvePointer(TOUT<T> &out, const Pointer &ptrval,
const FileDatabase &db, const Field &f,
bool non_recursive = false) const;
// --------------------------------------------------------
template <template <typename> class TOUT, typename T>
bool ResolvePointer(vector<TOUT<T>> &out, const Pointer &ptrval,
const FileDatabase &db, const Field &f, bool) const;
// --------------------------------------------------------
bool ResolvePointer(std::shared_ptr<FileOffset> &out, const Pointer &ptrval,
const FileDatabase &db, const Field &f, bool) const;
// --------------------------------------------------------
inline const FileBlockHead *LocateFileBlockForAddress(
const Pointer &ptrval,
const FileDatabase &db) const;
private:
// ------------------------------------------------------------------------------
template <typename T>
T *_allocate(std::shared_ptr<T> &out, size_t &s) const {
out = std::shared_ptr<T>(new T());
s = 1;
return out.get();
}
template <typename T>
T *_allocate(vector<T> &out, size_t &s) const {
out.resize(s);
return s ? &out.front() : nullptr;
}
// --------------------------------------------------------
template <int error_policy>
struct _defaultInitializer {
template <typename T, unsigned int N>
void operator()(T (&out)[N], const char * = nullptr) {
for (unsigned int i = 0; i < N; ++i) {
out[i] = T();
}
}
template <typename T, unsigned int N, unsigned int M>
void operator()(T (&out)[N][M], const char * = nullptr) {
for (unsigned int i = 0; i < N; ++i) {
for (unsigned int j = 0; j < M; ++j) {
out[i][j] = T();
}
}
}
template <typename T>
void operator()(T &out, const char * = nullptr) {
out = T();
}
};
private:
mutable size_t cache_idx;
};
// -------------------------------------------------------------------------------------------------------
template<>
struct Structure::_defaultInitializer<ErrorPolicy_Warn> {
template <typename T>
void operator()(T &out, const char *reason = "<add reason>") {
ASSIMP_LOG_WARN(reason);
// ... and let the show go on
_defaultInitializer<0 /*ErrorPolicy_Igno*/>()(out);
}
};
// -------------------------------------------------------------------------------------------------------
template<>
struct Structure::_defaultInitializer<ErrorPolicy_Fail> {
template <typename T>
void operator()(T & /*out*/, const char *message = "") {
// obviously, it is crucial that _DefaultInitializer is used
// only from within a catch clause.
throw DeadlyImportError("Constructing BlenderDNA Structure encountered an error: ", message);
}
};
// -------------------------------------------------------------------------------------------------------
template <>
inline bool Structure ::ResolvePointer<std::shared_ptr, ElemBase>(std::shared_ptr<ElemBase> &out,
const Pointer &ptrval,
const FileDatabase &db,
const Field &f,
bool) const;
template <> bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(
std::shared_ptr<ElemBase>& out, const Pointer & ptrval,
const FileDatabase& db, const Field&, bool) const;
template <> inline void Structure :: Convert<int> (int& dest,const FileDatabase& db) const;
template<> inline void Structure :: Convert<short> (short& dest,const FileDatabase& db) const;
template <> inline void Structure :: Convert<char> (char& dest,const FileDatabase& db) const;
template <> inline void Structure::Convert<unsigned char>(unsigned char& dest, const FileDatabase& db) const;
template <> inline void Structure :: Convert<float> (float& dest,const FileDatabase& db) const;
template <> inline void Structure :: Convert<double> (double& dest,const FileDatabase& db) const;
template <> inline void Structure :: Convert<Pointer> (Pointer& dest,const FileDatabase& db) const;
// -------------------------------------------------------------------------------
/** Represents the full data structure information for a single BLEND file.
* This data is extracted from the DNA1 chunk in the file.
* #DNAParser does the reading and represents currently the only place where
* DNA is altered.*/
// -------------------------------------------------------------------------------
class DNA {
public:
typedef void (Structure::*ConvertProcPtr)(
std::shared_ptr<ElemBase> in,
const FileDatabase &) const;
typedef std::shared_ptr<ElemBase> (
Structure::*AllocProcPtr)() const;
typedef std::pair<AllocProcPtr, ConvertProcPtr> FactoryPair;
public:
std::map<std::string, FactoryPair> converters;
vector<Structure> structures;
std::map<std::string, size_t> indices;
public:
// --------------------------------------------------------
/** Access a structure by its canonical name, the pointer version returns nullptr on failure
* while the reference version raises an error. */
inline const Structure &operator[](const std::string &ss) const;
inline const Structure *Get(const std::string &ss) const;
// --------------------------------------------------------
/** Access a structure by its index */
inline const Structure &operator[](const size_t i) const;
public:
// --------------------------------------------------------
/** Add structure definitions for all the primitive types,
* i.e. integer, short, char, float */
void AddPrimitiveStructures();
// --------------------------------------------------------
/** Fill the @c converters member with converters for all
* known data types. The implementation of this method is
* in BlenderScene.cpp and is machine-generated.
* Converters are used to quickly handle objects whose
* exact data type is a runtime-property and not yet
* known at compile time (consider Object::data).*/
void RegisterConverters();
// --------------------------------------------------------
/** Take an input blob from the stream, interpret it according to
* a its structure name and convert it to the intermediate
* representation.
* @param structure Destination structure definition
* @param db File database.
* @return A null pointer if no appropriate converter is available.*/
std::shared_ptr<ElemBase> ConvertBlobToStructure(
const Structure &structure,
const FileDatabase &db) const;
// --------------------------------------------------------
/** Find a suitable conversion function for a given Structure.
* Such a converter function takes a blob from the input
* stream, reads as much as it needs, and builds up a
* complete object in intermediate representation.
* @param structure Destination structure definition
* @param db File database.
* @return A null pointer in .first if no appropriate converter is available.*/
FactoryPair GetBlobToStructureConverter(
const Structure &structure,
const FileDatabase &db) const;
#if ASSIMP_BUILD_BLENDER_DEBUG_DNA
// --------------------------------------------------------
/** Dump the DNA to a text file. This is for debugging purposes.
* The output file is `dna.txt` in the current working folder*/
void DumpToFile();
#endif
// --------------------------------------------------------
/** Extract array dimensions from a C array declaration, such
* as `...[4][6]`. Returned string would be `...[][]`.
* @param out
* @param array_sizes Receive maximally two array dimensions,
* the second element is set to 1 if the array is flat.
* Both are set to 1 if the input is not an array.
* @throw DeadlyImportError if more than 2 dimensions are
* encountered. */
static void ExtractArraySize(
const std::string &out,
size_t array_sizes[2]);
};
// special converters for primitive types
template <>
inline void Structure ::Convert<int>(int &dest, const FileDatabase &db) const;
template <>
inline void Structure ::Convert<short>(short &dest, const FileDatabase &db) const;
template <>
inline void Structure ::Convert<char>(char &dest, const FileDatabase &db) const;
template <>
inline void Structure ::Convert<float>(float &dest, const FileDatabase &db) const;
template <>
inline void Structure ::Convert<double>(double &dest, const FileDatabase &db) const;
template <>
inline void Structure ::Convert<Pointer>(Pointer &dest, const FileDatabase &db) const;
// -------------------------------------------------------------------------------
/** Describes a master file block header. Each master file sections holds n
* elements of a certain SDNA structure (or otherwise unspecified data). */
// -------------------------------------------------------------------------------
struct FileBlockHead {
// points right after the header of the file block
StreamReaderAny::pos start;
std::string id;
size_t size;
// original memory address of the data
Pointer address;
// index into DNA
unsigned int dna_index;
// number of structure instances to follow
size_t num;
// file blocks are sorted by address to quickly locate specific memory addresses
bool operator<(const FileBlockHead &o) const {
return address.val < o.address.val;
}
// for std::upper_bound
operator const Pointer &() const {
return address;
}
};
// for std::upper_bound
inline bool operator<(const Pointer &a, const Pointer &b) {
return a.val < b.val;
}
// -------------------------------------------------------------------------------
/** Utility to read all master file blocks in turn. */
// -------------------------------------------------------------------------------
class SectionParser {
public:
// --------------------------------------------------------
/** @param stream Inout stream, must point to the
* first section in the file. Call Next() once
* to have it read.
* @param ptr64 Pointer size in file is 64 bits? */
SectionParser(StreamReaderAny &stream, bool ptr64) :
stream(stream), ptr64(ptr64) {
current.size = current.start = 0;
}
public:
// --------------------------------------------------------
const FileBlockHead &GetCurrent() const {
return current;
}
public:
// --------------------------------------------------------
/** Advance to the next section.
* @throw DeadlyImportError if the last chunk was passed. */
void Next();
public:
FileBlockHead current;
StreamReaderAny &stream;
bool ptr64;
};
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
// -------------------------------------------------------------------------------
/** Import statistics, i.e. number of file blocks read*/
// -------------------------------------------------------------------------------
class Statistics {
public:
Statistics() = default;
~Statistics() = default;
/// total number of fields we read
unsigned int fields_read;
/// total number of resolved pointers
unsigned int pointers_resolved;
/// number of pointers resolved from the cache
unsigned int cache_hits;
/// objects in FileData::cache
unsigned int cached_objects;
};
#endif
// -------------------------------------------------------------------------------
/** The object cache - all objects addressed by pointers are added here. This
* avoids circular references and avoids object duplication. */
// -------------------------------------------------------------------------------
template <template <typename> class TOUT>
class ObjectCache {
public:
typedef std::map<Pointer, TOUT<ElemBase>> StructureCache;
public:
explicit ObjectCache(const FileDatabase &db) : db(db) {
// currently there are only ~400 structure records per blend file.
// we read only a small part of them and don't cache objects
// which we don't need, so this should suffice.
caches.reserve(64);
}
// --------------------------------------------------------
/** Check whether a specific item is in the cache.
* @param s Data type of the item
* @param out Output pointer. Unchanged if the
* cache doesn't know the item yet.
* @param ptr Item address to look for. */
template <typename T>
void get( const Structure &s,TOUT<T> &out, const Pointer &ptr) const;
// --------------------------------------------------------
/** Add an item to the cache after the item has
* been fully read. Do not insert anything that
* may be faulty or might cause the loading
* to abort.
* @param s Data type of the item
* @param out Item to insert into the cache
* @param ptr address (cache key) of the item. */
template <typename T>
void set(const Structure &s,
const TOUT<T> &out,
const Pointer &ptr);
private:
mutable vector<StructureCache> caches;
const FileDatabase &db;
};
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
template <>
class ObjectCache<Blender::vector> {
public:
explicit ObjectCache(const FileDatabase &) {}
template <typename T>
void get(const Structure &, vector<T> &, const Pointer &) {}
template <typename T>
void set(const Structure &, const vector<T> &, const Pointer &) {}
};
#ifdef _MSC_VER
# pragma warning(disable : 4355)
#endif
// -------------------------------------------------------------------------------
/** Memory representation of a full BLEND file and all its dependencies. The
* output aiScene is constructed from an instance of this data structure. */
// -------------------------------------------------------------------------------
class FileDatabase {
template <template <typename> class TOUT>
friend class ObjectCache;
public:
FileDatabase() :
_cacheArrays(*this), _cache(*this), next_cache_idx() {}
Statistics &stats() const {
return _stats;
}
// For all our templates to work on both shared_ptr's and vector's
// using the same code, a dummy cache for arrays is provided. Actually,
// arrays of objects are never cached because we can't easily
// ensure their proper destruction.
template <typename T>
ObjectCache<std::shared_ptr> &cache(std::shared_ptr<T> & /*in*/) const {
return _cache;
}
template <typename T>
ObjectCache<vector> &cache(vector<T> & /*in*/) const {
return _cacheArrays;
}
public:
// publicly accessible fields
bool i64bit;
bool little;
DNA dna;
std::shared_ptr<StreamReaderAny> reader;
vector<FileBlockHead> entries;
private:
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
mutable Statistics _stats;
#endif
mutable ObjectCache<vector> _cacheArrays;
mutable ObjectCache<std::shared_ptr> _cache;
mutable size_t next_cache_idx;
};
#ifdef _MSC_VER
# pragma warning(default : 4355)
#endif
// -------------------------------------------------------------------------------
/** Factory to extract a #DNA from the DNA1 file block in a BLEND file. */
// -------------------------------------------------------------------------------
class DNAParser {
public:
/** Bind the parser to a empty DNA and an input stream */
explicit DNAParser(FileDatabase &db) : db(db) {
// empty
}
// --------------------------------------------------------
/** Locate the DNA in the file and parse it. The input
* stream is expected to point to the beginning of the DN1
* chunk at the time this method is called and is
* undefined afterwards.
* @throw DeadlyImportError if the DNA cannot be read.
* @note The position of the stream pointer is undefined
* afterwards.*/
void Parse();
/** Obtain a reference to the extracted DNA information */
const Blender::DNA &GetDNA() const {
return db.dna;
}
private:
FileDatabase &db;
};
/**
* @brief read CustomData's data to ptr to mem
* @param[out] out memory ptr to set
* @param[in] cdtype to read
* @param[in] cnt cnt of elements to read
* @param[in] db to read elements from
* @return true when ok
*/
bool readCustomData(std::shared_ptr<ElemBase> &out, int cdtype, size_t cnt, const FileDatabase &db);
} // namespace Blender
} // namespace Assimp
#include "BlenderDNA.inl"
#endif

View File

@@ -0,0 +1,847 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderDNA.inl
* @brief Blender `DNA` (file format specification embedded in
* blend file itself) loader.
*/
#ifndef INCLUDED_AI_BLEND_DNA_INL
#define INCLUDED_AI_BLEND_DNA_INL
#include <memory>
#include <assimp/TinyFormatter.h>
namespace Assimp {
namespace Blender {
//--------------------------------------------------------------------------------
const Field& Structure :: operator [] (const std::string& ss) const
{
std::map<std::string, size_t>::const_iterator it = indices.find(ss);
if (it == indices.end()) {
throw Error("BlendDNA: Did not find a field named `",ss,"` in structure `",name,"`");
}
return fields[(*it).second];
}
//--------------------------------------------------------------------------------
const Field* Structure :: Get (const std::string& ss) const
{
std::map<std::string, size_t>::const_iterator it = indices.find(ss);
return it == indices.end() ? nullptr : &fields[(*it).second];
}
//--------------------------------------------------------------------------------
const Field& Structure :: operator [] (const size_t i) const
{
if (i >= fields.size()) {
throw Error("BlendDNA: There is no field with index `",i,"` in structure `",name,"`");
}
return fields[i];
}
//--------------------------------------------------------------------------------
template <typename T> std::shared_ptr<ElemBase> Structure :: Allocate() const
{
return std::shared_ptr<T>(new T());
}
//--------------------------------------------------------------------------------
template <typename T> void Structure :: Convert(
std::shared_ptr<ElemBase> in,
const FileDatabase& db) const
{
Convert<T> (*static_cast<T*> ( in.get() ),db);
}
//--------------------------------------------------------------------------------
template <int error_policy, typename T, size_t M>
void Structure :: ReadFieldArray(T (& out)[M], const char* name, const FileDatabase& db) const
{
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
try {
const Field& f = (*this)[name];
const Structure& s = db.dna[f.type];
// is the input actually an array?
if (!(f.flags & FieldFlag_Array)) {
throw Error("Field `",name,"` of structure `",this->name,"` ought to be an array of size ",M);
}
db.reader->IncPtr(f.offset);
// size conversions are always allowed, regardless of error_policy
unsigned int i = 0;
for(; i < std::min(f.array_sizes[0],M); ++i) {
s.Convert(out[i],db);
}
for(; i < M; ++i) {
_defaultInitializer<ErrorPolicy_Igno>()(out[i]);
}
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out,e.what());
}
// and recover the previous stream position
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
}
//--------------------------------------------------------------------------------
template <int error_policy, typename T, size_t M, size_t N>
void Structure :: ReadFieldArray2(T (& out)[M][N], const char* name, const FileDatabase& db) const
{
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
try {
const Field& f = (*this)[name];
const Structure& s = db.dna[f.type];
// is the input actually an array?
if (!(f.flags & FieldFlag_Array)) {
throw Error("Field `",name,"` of structure `",
this->name,"` ought to be an array of size ",M,"*",N
);
}
db.reader->IncPtr(f.offset);
// size conversions are always allowed, regardless of error_policy
unsigned int i = 0;
for(; i < std::min(f.array_sizes[0],M); ++i) {
unsigned int j = 0;
for(; j < std::min(f.array_sizes[1],N); ++j) {
s.Convert(out[i][j],db);
}
for(; j < N; ++j) {
_defaultInitializer<ErrorPolicy_Igno>()(out[i][j]);
}
}
for(; i < M; ++i) {
_defaultInitializer<ErrorPolicy_Igno>()(out[i]);
}
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out,e.what());
}
// and recover the previous stream position
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
}
//--------------------------------------------------------------------------------
template <int error_policy, template <typename> class TOUT, typename T>
bool Structure :: ReadFieldPtr(TOUT<T>& out, const char* name, const FileDatabase& db,
bool non_recursive /*= false*/) const
{
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
Pointer ptrval;
const Field* f;
try {
f = &(*this)[name];
// sanity check, should never happen if the genblenddna script is right
if (!(f->flags & FieldFlag_Pointer)) {
throw Error("Field `",name,"` of structure `",
this->name,"` ought to be a pointer");
}
db.reader->IncPtr(f->offset);
Convert(ptrval,db);
// actually it is meaningless on which Structure the Convert is called
// because the `Pointer` argument triggers a special implementation.
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out,e.what());
out.reset();
return false;
}
// resolve the pointer and load the corresponding structure
const bool res = ResolvePointer(out,ptrval,db,*f, non_recursive);
if(!non_recursive) {
// and recover the previous stream position
db.reader->SetCurrentPos(old);
}
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
return res;
}
//--------------------------------------------------------------------------------
template <int error_policy, template <typename> class TOUT, typename T, size_t N>
bool Structure :: ReadFieldPtr(TOUT<T> (&out)[N], const char* name,
const FileDatabase& db) const
{
// XXX see if we can reduce this to call to the 'normal' ReadFieldPtr
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
Pointer ptrval[N];
const Field* f;
try {
f = &(*this)[name];
#ifdef _DEBUG
// sanity check, should never happen if the genblenddna script is right
if ((FieldFlag_Pointer|FieldFlag_Pointer) != (f->flags & (FieldFlag_Pointer|FieldFlag_Pointer))) {
throw Error("Field `",name,"` of structure `",
this->name,"` ought to be a pointer AND an array");
}
#endif // _DEBUG
db.reader->IncPtr(f->offset);
size_t i = 0;
for(; i < std::min(f->array_sizes[0],N); ++i) {
Convert(ptrval[i],db);
}
for(; i < N; ++i) {
_defaultInitializer<ErrorPolicy_Igno>()(ptrval[i]);
}
// actually it is meaningless on which Structure the Convert is called
// because the `Pointer` argument triggers a special implementation.
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out,e.what());
for(size_t i = 0; i < N; ++i) {
out[i].reset();
}
return false;
}
bool res = true;
for(size_t i = 0; i < N; ++i) {
// resolve the pointer and load the corresponding structure
res = ResolvePointer(out[i],ptrval[i],db,*f) && res;
}
// and recover the previous stream position
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
return res;
}
//--------------------------------------------------------------------------------
template <int error_policy, typename T>
void Structure :: ReadField(T& out, const char* name, const FileDatabase& db) const
{
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
try {
const Field& f = (*this)[name];
// find the structure definition pertaining to this field
const Structure& s = db.dna[f.type];
db.reader->IncPtr(f.offset);
s.Convert(out,db);
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out,e.what());
}
// and recover the previous stream position
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
}
//--------------------------------------------------------------------------------
// field parsing for raw untyped data (like CustomDataLayer.data)
template <int error_policy>
bool Structure::ReadCustomDataPtr(std::shared_ptr<ElemBase>&out, int cdtype, const char* name, const FileDatabase& db) const {
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
Pointer ptrval;
const Field* f;
try {
f = &(*this)[name];
// sanity check, should never happen if the genblenddna script is right
if (!(f->flags & FieldFlag_Pointer)) {
throw Error("Field `", name, "` of structure `",
this->name, "` ought to be a pointer");
}
db.reader->IncPtr(f->offset);
Convert(ptrval, db);
// actually it is meaningless on which Structure the Convert is called
// because the `Pointer` argument triggers a special implementation.
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out, e.what());
out.reset();
}
bool readOk = true;
if (ptrval.val) {
// get block for ptr
const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db);
db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val)));
// read block->num instances of given type to out
readOk = readCustomData(out, cdtype, block->num, db);
}
// and recover the previous stream position
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
return readOk;
}
//--------------------------------------------------------------------------------
template <int error_policy, template <typename> class TOUT, typename T>
bool Structure::ReadFieldPtrVector(vector<TOUT<T>>&out, const char* name, const FileDatabase& db) const {
out.clear();
const StreamReaderAny::pos old = db.reader->GetCurrentPos();
Pointer ptrval;
const Field* f;
try {
f = &(*this)[name];
// sanity check, should never happen if the genblenddna script is right
if (!(f->flags & FieldFlag_Pointer)) {
throw Error("Field `", name, "` of structure `",
this->name, "` ought to be a pointer");
}
db.reader->IncPtr(f->offset);
Convert(ptrval, db);
// actually it is meaningless on which Structure the Convert is called
// because the `Pointer` argument triggers a special implementation.
}
catch (const Error& e) {
_defaultInitializer<error_policy>()(out, e.what());
out.clear();
return false;
}
if (ptrval.val) {
// find the file block the pointer is pointing to
const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db);
db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val)));
// FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
// I really ought to improve StreamReader to work with 64 bit indices exclusively.
const Structure& s = db.dna[f->type];
for (size_t i = 0; i < block->num; ++i) {
TOUT<T> p(new T);
s.Convert(*p, db);
out.push_back(p);
}
}
db.reader->SetCurrentPos(old);
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().fields_read;
#endif
return true;
}
//--------------------------------------------------------------------------------
template <template <typename> class TOUT, typename T>
bool Structure :: ResolvePointer(TOUT<T>& out, const Pointer & ptrval, const FileDatabase& db,
const Field& f,
bool non_recursive /*= false*/) const
{
out.reset(); // ensure null pointers work
if (!ptrval.val) {
return false;
}
const Structure& s = db.dna[f.type];
// find the file block the pointer is pointing to
const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
// also determine the target type from the block header
// and check if it matches the type which we expect.
const Structure& ss = db.dna[block->dna_index];
if (ss != s) {
throw Error("Expected target to be of type `",s.name,
"` but seemingly it is a `",ss.name,"` instead"
);
}
// try to retrieve the object from the cache
db.cache(out).get(s,out,ptrval);
if (out) {
return true;
}
// seek to this location, but save the previous stream pointer.
const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
// FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
// I really ought to improve StreamReader to work with 64 bit indices exclusively.
// continue conversion after allocating the required storage
size_t num = block->size / ss.size;
T* o = _allocate(out,num);
// cache the object before we convert it to avoid cyclic recursion.
db.cache(out).set(s,out,ptrval);
// if the non_recursive flag is set, we don't do anything but leave
// the cursor at the correct position to resolve the object.
if (!non_recursive) {
for (size_t i = 0; i < num; ++i,++o) {
s.Convert(*o,db);
}
db.reader->SetCurrentPos(pold);
}
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
if(out) {
++db.stats().pointers_resolved;
}
#endif
return false;
}
//--------------------------------------------------------------------------------
inline bool Structure :: ResolvePointer( std::shared_ptr< FileOffset >& out, const Pointer & ptrval,
const FileDatabase& db,
const Field&,
bool) const
{
// Currently used exclusively by PackedFile::data to represent
// a simple offset into the mapped BLEND file.
out.reset();
if (!ptrval.val) {
return false;
}
// find the file block the pointer is pointing to
const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
out = std::shared_ptr< FileOffset > (new FileOffset());
out->val = block->start+ static_cast<size_t>((ptrval.val - block->address.val) );
return false;
}
//--------------------------------------------------------------------------------
template <template <typename> class TOUT, typename T>
bool Structure :: ResolvePointer(vector< TOUT<T> >& out, const Pointer & ptrval,
const FileDatabase& db,
const Field& f,
bool) const
{
// This is a function overload, not a template specialization. According to
// the partial ordering rules, it should be selected by the compiler
// for array-of-pointer inputs, i.e. Object::mats.
out.reset();
if (!ptrval.val) {
return false;
}
// find the file block the pointer is pointing to
const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
const size_t num = block->size / (db.i64bit?8:4);
// keep the old stream position
const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
bool res = false;
// allocate raw storage for the array
out.resize(num);
for (size_t i = 0; i< num; ++i) {
Pointer val;
Convert(val,db);
// and resolve the pointees
res = ResolvePointer(out[i],val,db,f) && res;
}
db.reader->SetCurrentPos(pold);
return res;
}
//--------------------------------------------------------------------------------
template <> bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(std::shared_ptr<ElemBase>& out,
const Pointer & ptrval,
const FileDatabase& db,
const Field&,
bool
) const
{
// Special case when the data type needs to be determined at runtime.
// Less secure than in the `strongly-typed` case.
out.reset();
if (!ptrval.val) {
return false;
}
// find the file block the pointer is pointing to
const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
// determine the target type from the block header
const Structure& s = db.dna[block->dna_index];
// try to retrieve the object from the cache
db.cache(out).get(s,out,ptrval);
if (out) {
return true;
}
// seek to this location, but save the previous stream pointer.
const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
// FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
// I really ought to improve StreamReader to work with 64 bit indices exclusively.
// continue conversion after allocating the required storage
DNA::FactoryPair builders = db.dna.GetBlobToStructureConverter(s,db);
if (!builders.first) {
// this might happen if DNA::RegisterConverters hasn't been called so far
// or if the target type is not contained in `our` DNA.
out.reset();
ASSIMP_LOG_WARN( "Failed to find a converter for the `",s.name,"` structure" );
return false;
}
// allocate the object hull
out = (s.*builders.first)();
// cache the object immediately to prevent infinite recursion in a
// circular list with a single element (i.e. a self-referencing element).
db.cache(out).set(s,out,ptrval);
// and do the actual conversion
(s.*builders.second)(out,db);
db.reader->SetCurrentPos(pold);
// store a pointer to the name string of the actual type
// in the object itself. This allows the conversion code
// to perform additional type checking.
out->dna_type = s.name.c_str();
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().pointers_resolved;
#endif
return false;
}
//--------------------------------------------------------------------------------
const FileBlockHead* Structure :: LocateFileBlockForAddress(const Pointer & ptrval, const FileDatabase& db) const
{
// the file blocks appear in list sorted by
// with ascending base addresses so we can run a
// binary search to locate the pointer quickly.
// NOTE: Blender seems to distinguish between side-by-side
// data (stored in the same data block) and far pointers,
// which are only used for structures starting with an ID.
// We don't need to make this distinction, our algorithm
// works regardless where the data is stored.
vector<FileBlockHead>::const_iterator it = std::lower_bound(db.entries.begin(),db.entries.end(),ptrval);
if (it == db.entries.end()) {
// this is crucial, pointers may not be invalid.
// this is either a corrupted file or an attempted attack.
throw DeadlyImportError("Failure resolving pointer 0x",
std::hex,ptrval.val,", no file block falls into this address range");
}
if (ptrval.val >= (*it).address.val + (*it).size) {
throw DeadlyImportError("Failure resolving pointer 0x",
std::hex,ptrval.val,", nearest file block starting at 0x",
(*it).address.val," ends at 0x",
(*it).address.val + (*it).size);
}
return &*it;
}
// ------------------------------------------------------------------------------------------------
// NOTE: The MSVC debugger keeps showing up this annoying `a cast to a smaller data type has
// caused a loss of data`-warning. Avoid this warning by a masking with an appropriate bitmask.
template <typename T> struct signless;
template <> struct signless<char> {typedef unsigned char type;};
template <> struct signless<short> {typedef unsigned short type;};
template <> struct signless<int> {typedef unsigned int type;};
template <> struct signless<unsigned char> { typedef unsigned char type; };
template <typename T>
struct static_cast_silent {
template <typename V>
T operator()(V in) {
return static_cast<T>(in & static_cast<typename signless<T>::type>(-1));
}
};
template <> struct static_cast_silent<float> {
template <typename V> float operator()(V in) {
return static_cast<float> (in);
}
};
template <> struct static_cast_silent<double> {
template <typename V> double operator()(V in) {
return static_cast<double>(in);
}
};
// ------------------------------------------------------------------------------------------------
template <typename T> inline void ConvertDispatcher(T& out, const Structure& in,const FileDatabase& db)
{
if (in.name == "int") {
out = static_cast_silent<T>()(db.reader->GetU4());
}
else if (in.name == "short") {
out = static_cast_silent<T>()(db.reader->GetU2());
}
else if (in.name == "char") {
out = static_cast_silent<T>()(db.reader->GetU1());
}
else if (in.name == "float") {
out = static_cast<T>(db.reader->GetF4());
}
else if (in.name == "double") {
out = static_cast<T>(db.reader->GetF8());
}
else {
throw DeadlyImportError("Unknown source for conversion to primitive data type: ", in.name);
}
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure :: Convert<int> (int& dest,const FileDatabase& db) const
{
ConvertDispatcher(dest,*this,db);
}
// ------------------------------------------------------------------------------------------------
template<> inline void Structure :: Convert<short> (short& dest,const FileDatabase& db) const
{
// automatic rescaling from short to float and vice versa (seems to be used by normals)
if (name == "float") {
float f = db.reader->GetF4();
if ( f > 1.0f )
f = 1.0f;
dest = static_cast<short>( f * 32767.f);
//db.reader->IncPtr(-4);
return;
}
else if (name == "double") {
dest = static_cast<short>(db.reader->GetF8() * 32767.);
//db.reader->IncPtr(-8);
return;
}
ConvertDispatcher(dest,*this,db);
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure :: Convert<char> (char& dest,const FileDatabase& db) const
{
// automatic rescaling from char to float and vice versa (seems useful for RGB colors)
if (name == "float") {
dest = static_cast<char>(db.reader->GetF4() * 255.f);
return;
}
else if (name == "double") {
dest = static_cast<char>(db.reader->GetF8() * 255.f);
return;
}
ConvertDispatcher(dest,*this,db);
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure::Convert<unsigned char>(unsigned char& dest, const FileDatabase& db) const
{
// automatic rescaling from char to float and vice versa (seems useful for RGB colors)
if (name == "float") {
dest = static_cast<unsigned char>(db.reader->GetF4() * 255.f);
return;
}
else if (name == "double") {
dest = static_cast<unsigned char>(db.reader->GetF8() * 255.f);
return;
}
ConvertDispatcher(dest, *this, db);
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure :: Convert<float> (float& dest,const FileDatabase& db) const
{
// automatic rescaling from char to float and vice versa (seems useful for RGB colors)
if (name == "char") {
dest = db.reader->GetI1() / 255.f;
return;
}
// automatic rescaling from short to float and vice versa (used by normals)
else if (name == "short") {
dest = db.reader->GetI2() / 32767.f;
return;
}
ConvertDispatcher(dest,*this,db);
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure :: Convert<double> (double& dest,const FileDatabase& db) const
{
if (name == "char") {
dest = db.reader->GetI1() / 255.;
return;
}
else if (name == "short") {
dest = db.reader->GetI2() / 32767.;
return;
}
ConvertDispatcher(dest,*this,db);
}
// ------------------------------------------------------------------------------------------------
template <> inline void Structure :: Convert<Pointer> (Pointer& dest,const FileDatabase& db) const
{
if (db.i64bit) {
dest.val = db.reader->GetU8();
//db.reader->IncPtr(-8);
return;
}
dest.val = db.reader->GetU4();
//db.reader->IncPtr(-4);
}
//--------------------------------------------------------------------------------
const Structure& DNA :: operator [] (const std::string& ss) const
{
std::map<std::string, size_t>::const_iterator it = indices.find(ss);
if (it == indices.end()) {
throw Error("BlendDNA: Did not find a structure named `",ss,"`");
}
return structures[(*it).second];
}
//--------------------------------------------------------------------------------
const Structure* DNA :: Get (const std::string& ss) const
{
std::map<std::string, size_t>::const_iterator it = indices.find(ss);
return it == indices.end() ? nullptr : &structures[(*it).second];
}
//--------------------------------------------------------------------------------
const Structure& DNA :: operator [] (const size_t i) const
{
if (i >= structures.size()) {
throw Error("BlendDNA: There is no structure with index `",i,"`");
}
return structures[i];
}
//--------------------------------------------------------------------------------
template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: get (
const Structure& s,
TOUT<T>& out,
const Pointer& ptr
) const {
if(s.cache_idx == static_cast<size_t>(-1)) {
s.cache_idx = db.next_cache_idx++;
caches.resize(db.next_cache_idx);
return;
}
typename StructureCache::const_iterator it = caches[s.cache_idx].find(ptr);
if (it != caches[s.cache_idx].end()) {
out = std::static_pointer_cast<T>( (*it).second );
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().cache_hits;
#endif
}
// otherwise, out remains untouched
}
//--------------------------------------------------------------------------------
template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: set (
const Structure& s,
const TOUT<T>& out,
const Pointer& ptr
) {
if(s.cache_idx == static_cast<size_t>(-1)) {
s.cache_idx = db.next_cache_idx++;
caches.resize(db.next_cache_idx);
}
caches[s.cache_idx][ptr] = std::static_pointer_cast<ElemBase>( out );
#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
++db.stats().cached_objects;
#endif
}
}
}
#endif

View File

@@ -0,0 +1,204 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderIntermediate.h
* @brief Internal utility structures for the BlenderLoader. It also serves
* as master include file for the whole (internal) Blender subsystem.
*/
#ifndef INCLUDED_AI_BLEND_INTERMEDIATE_H
#define INCLUDED_AI_BLEND_INTERMEDIATE_H
#include "BlenderLoader.h"
#include "BlenderDNA.h"
#include "BlenderScene.h"
#include <deque>
#include <assimp/material.h>
struct aiTexture;
namespace Assimp {
namespace Blender {
// --------------------------------------------------------------------
/** Mini smart-array to avoid pulling in even more boost stuff. usable with vector and deque */
// --------------------------------------------------------------------
template <template <typename,typename> class TCLASS, typename T>
struct TempArray {
typedef TCLASS< T*,std::allocator<T*> > mywrap;
TempArray() = default;
~TempArray () {
for(T* elem : arr) {
delete elem;
}
}
void dismiss() {
arr.clear();
}
mywrap* operator -> () {
return &arr;
}
operator mywrap& () {
return arr;
}
operator const mywrap& () const {
return arr;
}
mywrap& get () {
return arr;
}
const mywrap& get () const {
return arr;
}
T* operator[] (size_t idx) const {
return arr[idx];
}
T*& operator[] (size_t idx) {
return arr[idx];
}
private:
// no copy semantics
void operator= (const TempArray&) {
}
TempArray(const TempArray& /*arr*/) {
}
private:
mywrap arr;
};
#if defined(_MSC_VER) && _MSC_VER < 1900
# pragma warning(disable:4351)
#endif
// As counter-intuitive as it may seem, a comparator must return false for equal values.
// The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
struct ObjectCompare {
bool operator() (const Object* left, const Object* right) const {
return ::strncmp(left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
}
};
// When keeping objects in sets, sort them by their name.
typedef std::set<const Object*, ObjectCompare> ObjectSet;
// --------------------------------------------------------------------
/** ConversionData acts as intermediate storage location for
* the various ConvertXXX routines in BlenderImporter.*/
// --------------------------------------------------------------------
struct ConversionData
{
ConversionData(const FileDatabase& db)
: sentinel_cnt()
, next_texture()
, db(db)
{}
// As counter-intuitive as it may seem, a comparator must return false for equal values.
// The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
struct ObjectCompare {
bool operator() (const Object* left, const Object* right) const {
return ::strncmp( left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
}
};
ObjectSet objects;
TempArray <std::vector, aiMesh> meshes;
TempArray <std::vector, aiCamera> cameras;
TempArray <std::vector, aiLight> lights;
TempArray <std::vector, aiMaterial> materials;
TempArray <std::vector, aiTexture> textures;
// set of all materials referenced by at least one mesh in the scene
std::deque< std::shared_ptr< Material > > materials_raw;
// counter to name sentinel textures inserted as substitutes for procedural textures.
unsigned int sentinel_cnt;
// next texture ID for each texture type, respectively
unsigned int next_texture[aiTextureType_UNKNOWN+1];
// original file data
const FileDatabase& db;
};
#if defined(_MSC_VER) && _MSC_VER < 1900
# pragma warning(default:4351)
#endif
// ------------------------------------------------------------------------------------------------
inline const char* GetTextureTypeDisplayString(Tex::Type t)
{
switch (t) {
case Tex::Type_CLOUDS : return "Clouds";
case Tex::Type_WOOD : return "Wood";
case Tex::Type_MARBLE : return "Marble";
case Tex::Type_MAGIC : return "Magic";
case Tex::Type_BLEND : return "Blend";
case Tex::Type_STUCCI : return "Stucci";
case Tex::Type_NOISE : return "Noise";
case Tex::Type_PLUGIN : return "Plugin";
case Tex::Type_MUSGRAVE : return "Musgrave";
case Tex::Type_VORONOI : return "Voronoi";
case Tex::Type_DISTNOISE : return "DistortedNoise";
case Tex::Type_ENVMAP : return "EnvMap";
case Tex::Type_IMAGE : return "Image";
default:
break;
}
return "<Unknown>";
}
} // ! Blender
} // ! Assimp
#endif // ! INCLUDED_AI_BLEND_INTERMEDIATE_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderLoader.h
* @brief Declaration of the Blender 3D (*.blend) importer class.
*/
#pragma once
#ifndef INCLUDED_AI_BLEND_LOADER_H
#define INCLUDED_AI_BLEND_LOADER_H
#include <assimp/BaseImporter.h>
#include <assimp/LogAux.h>
#include <memory>
struct aiNode;
struct aiMesh;
struct aiLight;
struct aiCamera;
struct aiMaterial;
namespace Assimp {
// TinyFormatter.h
namespace Formatter {
template <typename T, typename TR, typename A>
class basic_formatter;
typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>> format;
} // namespace Formatter
// BlenderDNA.h
namespace Blender {
class FileDatabase;
struct ElemBase;
} // namespace Blender
// BlenderScene.h
namespace Blender {
struct Scene;
struct Object;
struct Collection;
struct Mesh;
struct Camera;
struct Lamp;
struct MTex;
struct Image;
struct Material;
} // namespace Blender
// BlenderIntermediate.h
namespace Blender {
struct ConversionData;
template <template <typename, typename> class TCLASS, typename T>
struct TempArray;
} // namespace Blender
// BlenderModifier.h
namespace Blender {
class BlenderModifierShowcase;
class BlenderModifier;
} // namespace Blender
// -------------------------------------------------------------------------------------------
/** Load blenders official binary format. The actual file structure (the `DNA` how they
* call it is outsourced to BlenderDNA.cpp/BlenderDNA.h. This class only performs the
* conversion from intermediate format to aiScene. */
// -------------------------------------------------------------------------------------------
class BlenderImporter final : public BaseImporter, public LogFunctions<BlenderImporter> {
public:
BlenderImporter();
~BlenderImporter() override;
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
protected:
const aiImporterDesc *GetInfo() const override;
void SetupProperties(const Importer *pImp) override;
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
void ParseBlendFile(Blender::FileDatabase &out, std::shared_ptr<IOStream> stream);
void ExtractScene(Blender::Scene &out, const Blender::FileDatabase &file);
void ParseSubCollection(const Blender::Scene &in, aiNode *root, const std::shared_ptr<Blender::Collection>& collection, Blender::ConversionData &conv_data);
void ConvertBlendFile(aiScene *out, const Blender::Scene &in, const Blender::FileDatabase &file);
private:
aiNode *ConvertNode(const Blender::Scene &in,
const Blender::Object *obj,
Blender::ConversionData &conv_info,
const aiMatrix4x4 &parentTransform);
// --------------------
void ConvertMesh(const Blender::Scene &in,
const Blender::Object *obj,
const Blender::Mesh *mesh,
Blender::ConversionData &conv_data,
Blender::TempArray<std::vector, aiMesh> &temp);
// --------------------
aiLight *ConvertLight(const Blender::Scene &in,
const Blender::Object *obj,
const Blender::Lamp *mesh,
Blender::ConversionData &conv_data);
// --------------------
aiCamera *ConvertCamera(const Blender::Scene &in,
const Blender::Object *obj,
const Blender::Camera *mesh,
Blender::ConversionData &conv_data);
// --------------------
void BuildDefaultMaterial(
Blender::ConversionData &conv_data);
// --------------------
void AddBlendParams(
aiMaterial *result,
const Blender::Material *source);
// --------------------
void BuildMaterials(
Blender::ConversionData &conv_data);
// --------------------
void ResolveTexture(
aiMaterial *out,
const Blender::Material *mat,
const Blender::MTex *tex,
Blender::ConversionData &conv_data);
// --------------------
void ResolveImage(
aiMaterial *out,
const Blender::Material *mat,
const Blender::MTex *tex,
const Blender::Image *img,
Blender::ConversionData &conv_data);
// --------------------
void AddSentinelTexture(
aiMaterial *out,
const Blender::Material *mat,
const Blender::MTex *tex,
Blender::ConversionData &conv_data);
// TODO: Move to a std::variant, once c++17 is supported.
struct StreamOrError {
std::shared_ptr<IOStream> stream;
std::shared_ptr<std::vector<char>> input;
std::string error;
};
// Returns either a stream (and optional input data for the stream) or
// an error if it can't parse the magic token.
StreamOrError ParseMagicToken(
const std::string &pFile,
IOSystem *pIOHandler) const;
private: // static stuff, mostly logging and error reporting.
// --------------------
static void CheckActualType(const Blender::ElemBase *dt,
const char *check);
// --------------------
static void NotSupportedObjectType(const Blender::Object *obj,
const char *type);
private:
Blender::BlenderModifierShowcase *modifier_cache;
}; // !class BlenderImporter
} // end of namespace Assimp
#endif // AI_UNREALIMPORTER_H_INC

View File

@@ -0,0 +1,300 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderModifier.cpp
* @brief Implementation of some blender modifiers (i.e subdivision, mirror).
*/
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
#include "BlenderModifier.h"
#include <assimp/SceneCombiner.h>
#include <assimp/Subdivision.h>
#include <assimp/scene.h>
#include <memory>
#include <functional>
using namespace Assimp;
using namespace Assimp::Blender;
template <typename T>
BlenderModifier *god() {
return new T();
}
// add all available modifiers here
typedef BlenderModifier *(*fpCreateModifier)();
static const fpCreateModifier creators[] = {
&god<BlenderModifier_Mirror>,
&god<BlenderModifier_Subdivision>,
nullptr // sentinel
};
// ------------------------------------------------------------------------------------------------
void BlenderModifierShowcase::ApplyModifiers(aiNode &out, ConversionData &conv_data, const Scene &in, const Object &orig_object) {
size_t cnt = 0u, ful = 0u;
// NOTE: this cast is potentially unsafe by design, so we need to perform type checks before
// we're allowed to dereference the pointers without risking to crash. We might still be
// invoking UB btw - we're assuming that the ModifierData member of the respective modifier
// structures is at offset sizeof(vftable) with no padding.
const SharedModifierData *cur = static_cast<const SharedModifierData *>(orig_object.modifiers.first.get());
for (; cur; cur = static_cast<const SharedModifierData *>(cur->modifier.next.get()), ++ful) {
ai_assert(cur->dna_type);
const Structure *s = conv_data.db.dna.Get(cur->dna_type);
if (!s) {
ASSIMP_LOG_WARN("BlendModifier: could not resolve DNA name: ", cur->dna_type);
continue;
}
// this is a common trait of all XXXMirrorData structures in BlenderDNA
const Field *f = s->Get("modifier");
if (!f || f->offset != 0) {
ASSIMP_LOG_WARN("BlendModifier: expected a `modifier` member at offset 0");
continue;
}
s = conv_data.db.dna.Get(f->type);
if (!s || s->name != "ModifierData") {
ASSIMP_LOG_WARN("BlendModifier: expected a ModifierData structure as first member");
continue;
}
// now, we can be sure that we should be fine to dereference *cur* as
// ModifierData (with the above note).
const ModifierData &dat = cur->modifier;
const fpCreateModifier *curgod = creators;
std::vector<BlenderModifier *>::iterator curmod = cached_modifiers->begin(), endmod = cached_modifiers->end();
for (; *curgod; ++curgod, ++curmod) { // allocate modifiers on the fly
if (curmod == endmod) {
cached_modifiers->push_back((*curgod)());
endmod = cached_modifiers->end();
curmod = endmod - 1;
}
BlenderModifier *const modifier = *curmod;
if (modifier->IsActive(dat)) {
modifier->DoIt(out, conv_data, *static_cast<const ElemBase *>(cur), in, orig_object);
cnt++;
curgod = nullptr;
break;
}
}
if (curgod) {
ASSIMP_LOG_WARN("Couldn't find a handler for modifier: ", dat.name);
}
}
// Even though we managed to resolve some or all of the modifiers on this
// object, we still can't say whether our modifier implementations were
// able to fully do their job.
if (ful) {
ASSIMP_LOG_DEBUG("BlendModifier: found handlers for ", cnt, " of ", ful, " modifiers on `", orig_object.id.name,
"`, check log messages above for errors");
}
}
// ------------------------------------------------------------------------------------------------
bool BlenderModifier_Mirror ::IsActive(const ModifierData &modin) {
return modin.type == ModifierData::eModifierType_Mirror;
}
// ------------------------------------------------------------------------------------------------
void BlenderModifier_Mirror ::DoIt(aiNode &out, ConversionData &conv_data, const ElemBase &orig_modifier,
const Scene & /*in*/,
const Object &orig_object) {
// hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers()
const MirrorModifierData &mir = static_cast<const MirrorModifierData &>(orig_modifier);
ai_assert(mir.modifier.type == ModifierData::eModifierType_Mirror);
std::shared_ptr<Object> mirror_ob = mir.mirror_ob.lock();
conv_data.meshes->reserve(conv_data.meshes->size() + out.mNumMeshes);
// XXX not entirely correct, mirroring on two axes results in 4 distinct objects in blender ...
// take all input meshes and clone them
for (unsigned int i = 0; i < out.mNumMeshes; ++i) {
aiMesh *mesh;
SceneCombiner::Copy(&mesh, conv_data.meshes[out.mMeshes[i]]);
const float xs = mir.flag & MirrorModifierData::Flags_AXIS_X ? -1.f : 1.f;
const float ys = mir.flag & MirrorModifierData::Flags_AXIS_Y ? -1.f : 1.f;
const float zs = mir.flag & MirrorModifierData::Flags_AXIS_Z ? -1.f : 1.f;
if (mirror_ob) {
const aiVector3D center(mirror_ob->obmat[3][0], mirror_ob->obmat[3][1], mirror_ob->obmat[3][2]);
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mVertices[j];
v.x = center.x + xs * (center.x - v.x);
v.y = center.y + ys * (center.y - v.y);
v.z = center.z + zs * (center.z - v.z);
}
} else {
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mVertices[j];
v.x *= xs;
v.y *= ys;
v.z *= zs;
}
}
if (mesh->mNormals) {
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mNormals[j];
v.x *= xs;
v.y *= ys;
v.z *= zs;
}
}
if (mesh->mTangents) {
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mTangents[j];
v.x *= xs;
v.y *= ys;
v.z *= zs;
}
}
if (mesh->mBitangents) {
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mBitangents[j];
v.x *= xs;
v.y *= ys;
v.z *= zs;
}
}
const float us = mir.flag & MirrorModifierData::Flags_MIRROR_U ? -1.f : 1.f;
const float vs = mir.flag & MirrorModifierData::Flags_MIRROR_V ? -1.f : 1.f;
for (unsigned int n = 0; mesh->HasTextureCoords(n); ++n) {
for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
aiVector3D &v = mesh->mTextureCoords[n][j];
v.x *= us;
v.y *= vs;
}
}
// Only reverse the winding order if an odd number of axes were mirrored.
if (xs * ys * zs < 0) {
for (unsigned int j = 0; j < mesh->mNumFaces; ++j) {
aiFace &face = mesh->mFaces[j];
for (unsigned int fi = 0; fi < face.mNumIndices / 2; ++fi)
std::swap(face.mIndices[fi], face.mIndices[face.mNumIndices - 1 - fi]);
}
}
conv_data.meshes->push_back(mesh);
}
unsigned int *nind = new unsigned int[out.mNumMeshes * 2];
std::copy(out.mMeshes, out.mMeshes + out.mNumMeshes, nind);
std::transform(out.mMeshes, out.mMeshes + out.mNumMeshes, nind + out.mNumMeshes,
[&out](unsigned int n) { return out.mNumMeshes + n; });
delete[] out.mMeshes;
out.mMeshes = nind;
out.mNumMeshes *= 2;
ASSIMP_LOG_INFO("BlendModifier: Applied the `Mirror` modifier to `",
orig_object.id.name, "`");
}
// ------------------------------------------------------------------------------------------------
bool BlenderModifier_Subdivision ::IsActive(const ModifierData &modin) {
return modin.type == ModifierData::eModifierType_Subsurf;
}
// ------------------------------------------------------------------------------------------------
void BlenderModifier_Subdivision ::DoIt(aiNode &out, ConversionData &conv_data, const ElemBase &orig_modifier,
const Scene & /*in*/,
const Object &orig_object) {
// hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers()
const SubsurfModifierData &mir = static_cast<const SubsurfModifierData &>(orig_modifier);
ai_assert(mir.modifier.type == ModifierData::eModifierType_Subsurf);
Subdivider::Algorithm algo;
switch (mir.subdivType) {
case SubsurfModifierData::TYPE_CatmullClarke:
algo = Subdivider::CATMULL_CLARKE;
break;
case SubsurfModifierData::TYPE_Simple:
ASSIMP_LOG_WARN("BlendModifier: The `SIMPLE` subdivision algorithm is not currently implemented, using Catmull-Clarke");
algo = Subdivider::CATMULL_CLARKE;
break;
default:
ASSIMP_LOG_WARN("BlendModifier: Unrecognized subdivision algorithm: ", mir.subdivType);
return;
};
std::unique_ptr<Subdivider> subd(Subdivider::Create(algo));
ai_assert(subd);
if (conv_data.meshes->empty()) {
return;
}
const size_t meshIndex = conv_data.meshes->size() - out.mNumMeshes;
if (meshIndex >= conv_data.meshes->size()) {
ASSIMP_LOG_ERROR("Invalid index detected.");
return;
}
aiMesh **const meshes = &conv_data.meshes[conv_data.meshes->size() - out.mNumMeshes];
std::unique_ptr<aiMesh *[]> tempmeshes(new aiMesh *[out.mNumMeshes]());
subd->Subdivide(meshes, out.mNumMeshes, tempmeshes.get(), std::max(mir.renderLevels, mir.levels), true);
std::copy(tempmeshes.get(), tempmeshes.get() + out.mNumMeshes, meshes);
ASSIMP_LOG_INFO("BlendModifier: Applied the `Subdivision` modifier to `",
orig_object.id.name, "`");
}
#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

View File

@@ -0,0 +1,152 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2025, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file BlenderModifier.h
* @brief Declare dedicated helper classes to simulate some blender modifiers (i.e. mirror)
*/
#ifndef INCLUDED_AI_BLEND_MODIFIER_H
#define INCLUDED_AI_BLEND_MODIFIER_H
#include "BlenderIntermediate.h"
namespace Assimp {
namespace Blender {
// -------------------------------------------------------------------------------------------
/**
* Dummy base class for all blender modifiers. Modifiers are reused between imports, so
* they should be stateless and not try to cache model data.
*/
// -------------------------------------------------------------------------------------------
class BlenderModifier {
public:
/**
* The class destructor, virtual.
*/
virtual ~BlenderModifier() = default;
// --------------------
/**
* Check if *this* modifier is active, given a ModifierData& block.
*/
virtual bool IsActive( const ModifierData& /*modin*/) {
return false;
}
// --------------------
/**
* Apply the modifier to a given output node. The original data used
* to construct the node is given as well. Not called unless IsActive()
* was called and gave positive response.
*/
virtual void DoIt(aiNode& /*out*/,
ConversionData& /*conv_data*/,
const ElemBase& orig_modifier,
const Scene& /*in*/,
const Object& /*orig_object*/
) {
ASSIMP_LOG_INFO("This modifier is not supported, skipping: ",orig_modifier.dna_type );
return;
}
};
// -------------------------------------------------------------------------------------------
/**
* Manage all known modifiers and instance and apply them if necessary
*/
// -------------------------------------------------------------------------------------------
class BlenderModifierShowcase {
public:
// --------------------
/** Apply all requested modifiers provided we support them. */
void ApplyModifiers(aiNode& out,
ConversionData& conv_data,
const Scene& in,
const Object& orig_object
);
private:
TempArray< std::vector,BlenderModifier > cached_modifiers;
};
// MODIFIERS /////////////////////////////////////////////////////////////////////////////////
// -------------------------------------------------------------------------------------------
/**
* Mirror modifier. Status: implemented.
*/
// -------------------------------------------------------------------------------------------
class BlenderModifier_Mirror final : public BlenderModifier {
public:
// --------------------
virtual bool IsActive( const ModifierData& modin);
// --------------------
virtual void DoIt(aiNode& out,
ConversionData& conv_data,
const ElemBase& orig_modifier,
const Scene& in,
const Object& orig_object
) ;
};
// -------------------------------------------------------------------------------------------
/** Subdivision modifier. Status: dummy. */
// -------------------------------------------------------------------------------------------
class BlenderModifier_Subdivision final : public BlenderModifier {
public:
// --------------------
virtual bool IsActive( const ModifierData& modin);
// --------------------
virtual void DoIt(aiNode& out,
ConversionData& conv_data,
const ElemBase& orig_modifier,
const Scene& in,
const Object& orig_object
) ;
};
}
}
#endif // !INCLUDED_AI_BLEND_MODIFIER_H

Some files were not shown because too many files have changed in this diff Show More