btCollisionShape* lantern::LoadSTLAsCollisionShape(const std::string filePath, bool staticMesh, bool flipYZ){
//TODO: Condense the if and else stuff into one function using suppliers or smth
if(staticMesh){
btTriangleMesh meshInterface = new btTriangleMesh();
CommonFileIOInterface fileIO = new b3BulletDefaultFileIO();
int fileHandle = fileIO->fileOpen(&(filePath.at(0)), "rb");
int yIndex = (flipYZ) ? 2 : 1;
int zIndex = (flipYZ) ? 1 : 2;
if (fileHandle>=0)
{
int size = 0;
size = fileIO->getFileSize(fileHandle);
{
if (size>=0)
{
char* memoryBuffer = new char[size + 1];
int actualBytesRead = fileIO->fileRead(fileHandle, memoryBuffer, size);
if (actualBytesRead != size)
{
throw std::runtime_error("Error reading STL file: " + filePath + ". Actual file size does not match expected size.");
}
else
{
int numTriangles = *(int*)&memoryBuffer[80];
if (numTriangles)
{
{
//perform a sanity check instead of crashing on invalid triangles/STL files
int expectedBinaryFileSize = numTriangles * 50 + 84;
if (expectedBinaryFileSize != size)
{
delete[] memoryBuffer;
fileIO->fileClose(fileHandle);
throw std::runtime_error(
"Error reading STL file: " + filePath + ". Actual file size does not match expected size.");
return 0;
}
}
int index = 0;
printf("Loading %d triangles...\n", numTriangles);
auto startTime = std::chrono::steady_clock::now();
auto lastLogTime = startTime;
int trianglesProcessed = 0;
for (int i = 0; i < numTriangles; i++)
{
char* curPtr = &memoryBuffer[84 + i * 50];
TempSTLTriangle tmp;
memcpy(&tmp, curPtr, sizeof(TempSTLTriangle));
meshInterface -> addTriangle(
btVector3(
tmp.vertex0[0],
tmp.vertex0[yIndex],
tmp.vertex0[zIndex]),
btVector3(
tmp.vertex1[0],
tmp.vertex1[yIndex],
tmp.vertex1[zIndex]),
btVector3(
tmp.vertex2[0],
tmp.vertex2[yIndex],
tmp.vertex2[zIndex]),
true);
trianglesProcessed++;
// Log every 5 seconds
auto currentTime = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(currentTime - lastLogTime).count();
if (elapsed >= 5)
{
double trianglesPerSecond = trianglesProcessed / (double)elapsed;
double percentComplete = (i + 1) * 100.0 / numTriangles;
printf("Progress: %d/%d triangles (%.1f%%) - %.2f triangles/sec\n",
i + 1, numTriangles, percentComplete, trianglesPerSecond);
lastLogTime = currentTime;
trianglesProcessed = 0;
}
}
printf("Completed: %d/%d triangles (100.0%%)\n", numTriangles, numTriangles);
}
}
delete[] memoryBuffer;
}
}
}
fileIO->fileClose(fileHandle);
delete fileIO;
return new btBvhTriangleMeshShape(meshInterface, true);
}
else{
btConvexHullShape* shape = new btConvexHullShape();
CommonFileIOInterface* fileIO = new b3BulletDefaultFileIO();
int fileHandle = fileIO->fileOpen(&(filePath.at(0)), "rb");
int yIndex = (flipYZ) ? 2 : 1;
int zIndex = (flipYZ) ? 1 : 2;
if (fileHandle>=0)
{
int size = 0;
size = fileIO->getFileSize(fileHandle);
{
if (size>=0)
{
char* memoryBuffer = new char[size + 1];
int actualBytesRead = fileIO->fileRead(fileHandle, memoryBuffer, size);
if (actualBytesRead != size)
{
throw std::runtime_error("Error reading STL file: " + filePath + ". Actual file size does not match expected size.");
}
else
{
int numTriangles = *(int*)&memoryBuffer[80];
if (numTriangles)
{
{
//perform a sanity check instead of crashing on invalid triangles/STL files
int expectedBinaryFileSize = numTriangles * 50 + 84;
if (expectedBinaryFileSize != size)
{
delete[] memoryBuffer;
fileIO->fileClose(fileHandle);
return 0;
}
}
int index = 0;
printf("Loading %d triangles...\n", numTriangles);
auto startTime = std::chrono::steady_clock::now();
auto lastLogTime = startTime;
int trianglesProcessed = 0;
for (int i = 0; i < numTriangles; i++)
{
char* curPtr = &memoryBuffer[84 + i * 50];
TempSTLTriangle tmp;
memcpy(&tmp, curPtr, sizeof(TempSTLTriangle));
shape -> addPoint(btVector3(
tmp.vertex0[0],
tmp.vertex0[yIndex],
tmp.vertex0[zIndex]));
shape -> addPoint(btVector3(
tmp.vertex1[0],
tmp.vertex1[yIndex],
tmp.vertex1[zIndex]));
shape -> addPoint(btVector3(
tmp.vertex2[0],
tmp.vertex2[yIndex],
tmp.vertex2[zIndex]));
trianglesProcessed++;
// Log every 5 seconds
auto currentTime = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(currentTime - lastLogTime).count();
if (elapsed >= 5)
{
double trianglesPerSecond = trianglesProcessed / (double)elapsed;
double percentComplete = (i + 1) * 100.0 / numTriangles;
printf("Progress: %d/%d triangles (%.1f%%) - %.2f triangles/sec\n",
i + 1, numTriangles, percentComplete, trianglesPerSecond);
lastLogTime = currentTime;
trianglesProcessed = 0;
}
}
printf("Completed: %d/%d triangles (100.0%%)\n", numTriangles, numTriangles);
}
}
delete[] memoryBuffer;
}
}
}
fileIO->fileClose(fileHandle);
delete fileIO;
return shape;
}
As mentioned in #51, I am using both C++ and Java for a project. One of the things I have done is adapt Bullet's STL importer example to be a JNI method(yes, I could have done it through Java but I just wanted to try making my own JNI method to gain experience). From my testing, when I use it in native code it is very slow when importing one of the larger STL files I work with(80k ish triangles). It took roughly 1 minute. However, when I ran the same exact code as a part of the JNI, no alterations or anything, the file was imported properly in less than 5 seconds. Is the reason for this due to any optimizations performed on the Java side of Libbulletjme, or is it because of JNI specific optimizations inherent to Java itself?
Code for importer:
#include "STLImporter.h"
#include
btCollisionShape* lantern::LoadSTLAsCollisionShape(const std::string filePath, bool staticMesh, bool flipYZ){
//TODO: Condense the if and else stuff into one function using suppliers or smth
if(staticMesh){
btTriangleMesh meshInterface = new btTriangleMesh();
CommonFileIOInterface fileIO = new b3BulletDefaultFileIO();
int fileHandle = fileIO->fileOpen(&(filePath.at(0)), "rb");
}
Code for JNI:
JNIEXPORT jlong JNICALL Java_com_vendor_lantern_util_importers_STLImporter_loadSTLAsCollisionShape(
JNIEnv *pEnv, jclass, jstring filename) {
jmeClasses::initJavaClasses(pEnv);
const char *nativeFilename = pEnv->GetStringUTFChars(filename, 0);
NULL_CHK(pEnv, nativeFilename, "The filename string is null.", 0)
}