Compare commits

...

3 Commits

Author SHA1 Message Date
dqn 691109e12a save mesh info 2025-09-09 18:57:37 +08:00
dqn cd6038ea13 sqlite 2025-09-09 16:50:02 +08:00
dqn 72c0fdc1cb ctx 2025-09-08 15:51:25 +08:00
10 changed files with 331 additions and 51 deletions

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
*.o
*.exe
.vscode/
.idea/
.idea/
*.dump
out/

View File

@ -7,10 +7,13 @@ fn main() {
// .flag("-std=c++11")
.warnings(false)
.include("./cpp_src/include")
.file("./cpp_src/tools.cpp")
.file("./cpp_src/db_ops.cpp")
.file("./cpp_src/fbx_parse.cpp")
.file("./cpp_src/fbx_wrap.cpp")
.compile("fbx_parse");
// -------------
// -------------
println!("cargo:rustc-link-search=native=./cpp_src/lib");
println!("cargo:rustc-link-lib=fbxsdk");
println!("cargo:rustc-link-lib=sqlite3");
}

153
cpp_src/db_ops.cpp Normal file
View File

@ -0,0 +1,153 @@
#include <db_ops.h>
#include <string>
#include <cstring>
#include <tools.h>
using namespace std;
sqlite3 *create_db(const char *db_path, bool overwrite_file)
{
if (overwrite_file)
{
if (remove(db_path) != 0 && errno != ENOENT)
{
fprintf(stderr, "Failed to remove old DB file: %s\n", strerror(errno));
return NULL;
}
}
sqlite3 *db = NULL;
int result_code = sqlite3_open(db_path, &db);
if (result_code != SQLITE_OK)
{
fprintf(stderr, "Failed to open DB: %s[%d]\n", sqlite3_errmsg(db), result_code);
sqlite3_close(db);
return NULL;
}
return db;
}
void create_tables(sqlite3 *db)
{
const char *sql_meshes = R"(
CREATE TABLE IF NOT EXISTS meshes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
vertex_count INTEGER NOT NULL,
index_count INTEGER NOT NULL,
fbx_id TEXT UNIQUE NOT NULL
);
)";
const char *sql_materials = R"(
CREATE TABLE IF NOT EXISTS materials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
shader_name TEXT,
fbx_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
)";
const char *sql_vertices = R"(
CREATE TABLE IF NOT EXISTS vertices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mesh_id INTEGER NOT NULL,
position_x REAL NOT NULL,
position_y REAL NOT NULL,
position_z REAL NOT NULL,
normal_x REAL,
normal_y REAL,
normal_z REAL,
uv_u REAL,
uv_v REAL,
FOREIGN KEY(mesh_id) REFERENCES meshes(id) ON DELETE CASCADE
);
)";
const char *sql_indices = R"(
CREATE TABLE IF NOT EXISTS indices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mesh_id INTEGER NOT NULL,
value INTEGER NOT NULL,
FOREIGN KEY(mesh_id) REFERENCES meshes(id) ON DELETE CASCADE
);
)";
const char *sql_material_props = R"(
CREATE TABLE IF NOT EXISTS material_properties (
id INTEGER PRIMARY KEY AUTOINCREMENT,
material_id INTEGER NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
type TEXT NOT NULL,
FOREIGN KEY(material_id) REFERENCES materials(id) ON DELETE CASCADE,
UNIQUE(material_id, key)
);
)";
const char *sql[] = {sql_meshes, sql_materials, sql_vertices, sql_indices, sql_material_props};
for (int i = 0; i < sizeof(sql) / sizeof(sql[0]); ++i)
{
printf("开始执行:%s", sql[i]);
// 检查前置条件数据库连接和SQL语句必须有效
if (!db)
{
printf("db 是空的");
// 处理空连接错误如log.Fatalf("db connection is NULL")
return; // 或抛异常终止
}
if (!sql[i] || strlen(sql[i]) == 0)
{
printf("SQL Query 是空的");
// 处理空SQL错误如log.Errorf("sql[%d] is NULL/empty", i)
continue; // 或终止
}
char *err_msg = nullptr;
int ret = sqlite3_exec(db, sql[i], nullptr, nullptr, &err_msg);
printf("db ret: %d, err_msg: %s\n", ret, err_msg);
if (ret != SQLITE_OK)
{
// 必须处理错误记录具体SQL和错误信息
// log.Criticalf("SQL error (i=%d): %s, SQL: %s", i, err_msg ? err_msg : "unknown", sql[i]);
sqlite3_free(err_msg); // 释放错误信息
return; // 或根据场景决定是否继续
}
// 成功执行无需释放err_msgsqlite3_exec成功时err_msg为NULL
}
}
// 函数定义
int save_mesh_to_table(FttContext* ctx,
sqlite3* db,
uint id,
const std::string& name,
int vertex_count, int index_count,
const std::string& fbx_id)
{
if (!db || name.empty() || fbx_id.empty()) return -1;
sqlite3_stmt* stmt = nullptr;
const char* sql = "INSERT OR IGNORE INTO meshes (id, name, vertex_count, index_count, fbx_id) VALUES (?, ?, ?, ?, ?);";
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL prepare failed: %s\n", sqlite3_errmsg(db));
return rc;
}
sqlite3_bind_int64(stmt, 1, id);
sqlite3_bind_text(stmt, 2, name.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 3, vertex_count);
sqlite3_bind_int(stmt, 4, index_count);
sqlite3_bind_text(stmt, 5, fbx_id.c_str(), -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE && rc != SQLITE_CONSTRAINT) {
fprintf(stderr, "SQL step failed: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
return rc == SQLITE_DONE ? 0 : -1;
}

View File

@ -2,14 +2,15 @@
#include <stdio.h>
// buz
#include <fbx_wrap.h>
#include <db_ops.h>
using namespace std;
extern "C" {
// 定义给Rust调用的接口函数
void fbx_parse(const char* fbx_path)
void fbx_parse(FttContext* ctx)
{
printf("%s\n", fbx_path);
printf("%s\n", ctx->fbx_file);
FbxManager* lSdkManager = FbxManager::Create();
if (lSdkManager){
printf("FbxManager created\n");
@ -23,7 +24,7 @@ extern "C" {
printf("FbxScene creation failed.\n");
}
FbxImporter* lImporter = FbxImporter::Create(lSdkManager, "");
bool s = lImporter->Initialize(fbx_path, -1, lSdkManager->GetIOSettings());
bool s = lImporter->Initialize(ctx->fbx_file, -1, lSdkManager->GetIOSettings());
if (s){
printf("FbxImporter initialized\n");
} else{
@ -34,16 +35,29 @@ extern "C" {
FbxNode* lRootNode = lScene->GetRootNode();
if (lRootNode) {
each_node(lRootNode, 0);
each_node(ctx, lRootNode, 0);
} else {
printf("No root node found in the scene.\n");
}
}
void load_fbx_mesh_to_db(const char* fbx_path)
void load_fbx_mesh_to_db( FttContext* ctx)
{
FbxNode* root = get_fbx_root_node(fbx_path);
each_node(root, 0);
// get mutable data
auto temp = ctx_from_const(ctx);
FttContext* mut_ctx = &temp;
// 按fbx名称生成 ${ctx.out_dir}/sqlite3.db
// 初始化db赋值到ctx.db
sqlite3* db = create_db((std::string(ctx->out_dir) + "/db.sqlite3").c_str(), true);
if (db == NULL) {
return;
}
mut_ctx->db = db;
// init db tables
create_tables(mut_ctx->db);
FbxNode* root = get_fbx_root_node(ctx->fbx_file);
each_node(mut_ctx, root, 0);
}
}

View File

@ -1,29 +1,31 @@
#include "fbx_wrap.h"
#include <fbx_wrap.h>
#include <stdio.h>
#include <iostream>
#include <db_ops.h>
#include <tools.h>
using namespace std;
int load_triangles(fbxsdk::FbxNodeAttribute* attr, std::vector<Triangle>& triangles) {
int load_triangles(fbxsdk::FbxNodeAttribute* attr, std::vector<V3>& indices, std::vector<V3>& triangles) {
if (!attr || attr->GetAttributeType() != FbxNodeAttribute::eMesh) return false;
FbxMesh* mesh = static_cast<FbxMesh*>(attr);
// 顶点数据
// int ctrlPointCount = mesh->GetControlPointsCount();
// vertices.assign(mesh->GetControlPoints(), mesh->GetControlPoints() + ctrlPointCount);
// 三角化并提取三角形索引
FbxGeometryConverter converter(mesh->GetFbxManager());
if (!converter.Triangulate(mesh, true)) return false; // 强制三角化
int polyCount = mesh->GetPolygonCount();
triangles.reserve(polyCount);
for (int i = 0; i < polyCount; ++i) {
if (mesh->GetPolygonSize(i) != 3) continue; // 跳过非三角形(理论上不会存在)
int i0 = mesh->GetPolygonVertex(i, 0);
int i1 = mesh->GetPolygonVertex(i, 1);
int i2 = mesh->GetPolygonVertex(i, 2);
triangles.emplace_back(i0, i1, i2);
int vertexCount = mesh->GetPolygonSize(i);
std::vector<int> polyIndices;
for (int j = 0; j < vertexCount; ++j) {
polyIndices.push_back(mesh->GetPolygonVertex(i, j));
}
for (int j = 2; j < vertexCount; ++j) {
indices.push_back({polyIndices[0], polyIndices[j-1], polyIndices[j]});
for (int k : {0, j-1, j}) {
FbxVector4 pos = mesh->GetControlPointAt(polyIndices[k]);
triangles.push_back({(float)pos[0], (float)pos[1], (float)pos[2]});
}
}
}
return true;
return 0;
}
FbxNode* get_fbx_root_node(const char* fbx_path)
@ -55,7 +57,7 @@ FbxNode* get_fbx_root_node(const char* fbx_path)
}
void each_node(FbxNode* parent_node, int level)
void each_node(FttContext* ctx, FbxNode* parent_node, int level)
{
// info
print_node_info(parent_node, level);
@ -65,14 +67,20 @@ void each_node(FbxNode* parent_node, int level)
FbxNodeAttribute* attr = parent_node->GetNodeAttributeByIndex(i);
if (attr && attr->GetAttributeType() == FbxNodeAttribute::eMesh)
{
std::vector<Triangle> triangles;
int count = load_triangles(attr, triangles);
printf("triangles: %ld;", triangles.size());
std::cout << "("
<< get<0>(triangles[0]) << ", "
<< get<1>(triangles[0]) << ", "
<< get<2>(triangles[0]) << ")"
<< std::endl;
std::vector<V3> vertices;
std::vector<V3> triangles;
int count = load_triangles(attr, vertices, triangles);
uint id = attr->GetUniqueID();
printf("[%ld]triangles: %ld\n", id, triangles.size());
// save data to database
sqlite3* db = ctx->db;
// todo save mesh into db
save_mesh_to_table(ctx, ctx->db,
id,
attr->GetName(),
vertices.size(), triangles.size(),
parent_node->GetNameOnly().Buffer());
}
}
@ -83,7 +91,7 @@ void each_node(FbxNode* parent_node, int level)
for (int i = 0; i < parent_node->GetChildCount(); i++)
{
FbxNode* child = parent_node->GetChild(i);
each_node(child, level + 1);
each_node(ctx, child, level + 1);
}
}
}

35
cpp_src/include/db_ops.h Normal file
View File

@ -0,0 +1,35 @@
#include <tools.h>
using namespace std;
/**
* @brief SQLite数据库连接
* @param db_path
* @param overwrite_file true则删除已有文件后创建新数据库
* @return sqlite3句柄NULL
*/
sqlite3* create_db(const char* db_path, bool overwrite_file);
/**
* @brief
* @param db SQLite数据库句柄
* @note db参数有效且已成功打开
*/
void create_tables(sqlite3* db);
///////////////////////////
//////// 数据保存 /////////
///////////////////////////
/**
* @brief
* @param ctx FTT上下文句柄/
* @param db SQLite数据库连接
* @param name ()
* @param vertex_count
* @param index_count
* @param fbx_id FBX文件ID
* @return SQLITE_OKSQLite错误码(<0)
* @note 1. ctx/db非NULLname/fbx_id非空
* 2. name会触发唯一键冲突
*/
int save_mesh_to_table(FttContext *ctx, sqlite3 *db, uint id, const std::string &name, int vertex_count, int index_count, const std::string &fbx_id);

View File

@ -1,13 +1,8 @@
#include <fbxsdk.h>
#include <vector>
#include <tools.h>
using namespace std;
using Triangle = std::tuple<int, int, int>;
struct Context {
};
using V3 = std::tuple<int, int, int>;
/**
* FBX节点树结构
@ -16,7 +11,7 @@ struct Context {
* @note 访parent_node的所有直接子节点和间接子节点
* @warning parent_node无效
*/
void each_node(FbxNode* parent_node, int level);
void each_node(FttContext* ctx, FbxNode* parent_node, int level);
/**
* FBX节点的基本信息
* @param node nullptr时输出警告日志
@ -25,7 +20,22 @@ void each_node(FbxNode* parent_node, int level);
* @example 2" |-- NodeName (Type: Mesh, Children: 3)"
*/
void print_node_info(FbxNode* node, int level);
/**
* FBX文件加载并返回根节点
* @param fbx_path FBX文件路径
* @return FbxNode* nullptr
* @note FBX SDK环境并自动释放资源
* @warning
*/
FbxNode* get_fbx_root_node(const char* fbx_path);
int load_triangles(fbxsdk::FbxNodeAttribute* attr, std::vector<Triangle>& triangles);
/**
* @brief FBX网格属性加载顶点数据和三角化索引
* @param[in] attr FBX节点属性FbxMesh类型
* @param[out] vertices Triangle存储3个FbxVector4顶点
* @param[out] triangles Triangle存储3个顶点索引
* @return bool truefalse
* @note
*/
int load_triangles(fbxsdk::FbxNodeAttribute* attr, std::vector<V3>& indices, std::vector<V3>& triangles);

23
cpp_src/include/tools.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef _TOOLS_H_
#define _TOOLS_H_
#include <fbxsdk.h>
#include <tuple>
#include <vector>
#include <sqlite3.h>
#include <string>
using namespace std;
typedef struct {
char* fbx_file;
char* out_dir;
sqlite3* db;
} FttContext;
FttContext ctx_from_const(const FttContext* ctx);
#endif

11
cpp_src/tools.cpp Normal file
View File

@ -0,0 +1,11 @@
#include <tools.h>
FttContext ctx_from_const(const FttContext* ctx) {
FttContext mut_ctx;
mut_ctx.db = ctx->db;
mut_ctx.fbx_file = ctx->fbx_file;
mut_ctx.out_dir = ctx->out_dir;
return mut_ctx;
}

View File

@ -1,19 +1,40 @@
use std::{ffi::CString, os::raw::c_char};
fn main() {
convert_fbx_to_3dtiles("/mnt/f/BaiduNetdiskDownload/绿科模型/绿科-三维视图-{三维}111.fbx");
convert_fbx_to_3dtiles("/root/src/fbx_to_3dtiles/out/绿科-三维视图-{三维}111.fbx",
"/root/src/fbx_to_3dtiles/out/lk");
}
#[repr(C)]
pub struct FttContext {
pub fbx_file: *mut c_char,
pub out_dir: *mut c_char
}
unsafe extern "C" {
pub unsafe fn fbx_parse(name_in: *const u8);
pub unsafe fn load_fbx_mesh_to_db(path: *const u8);
pub unsafe fn load_fbx_mesh_to_db(ctx: *const FttContext);
}
fn convert_fbx_to_3dtiles(fbx_path: &str) {
fn convert_fbx_to_3dtiles(fbx_path: &str, out_dir: &str) {
let ctx = &FttContext{
fbx_file: CString::new(fbx_path).unwrap().into_raw(),
out_dir: CString::new(out_dir).unwrap().into_raw()
};
unsafe {
// fbx_parse(fbx_path.as_ptr() as *const u8);
// 1. load to disk
load_fbx_mesh_to_db(fbx_path.as_ptr() as *const u8);
// fbx_path.as_ptr() as *const u8
load_fbx_mesh_to_db(ctx);
// free
let _ = CString::from_raw(ctx.fbx_file);
let _ = CString::from_raw(ctx.out_dir);
}
// unsafe {