Introduce VRML format (.wrl and .x3dv) 3D model support (#5857)

- Introduce VRML format (.wrl and .x3dv) 3D model support
- Add samples
---------

Co-authored-by: Steve M <praktique-tellypresence@yahoo.com>
Co-authored-by: Kim Kulling <kimkulling@users.noreply.github.com>
This commit is contained in:
Steve M
2024-12-30 02:56:18 -08:00
committed by GitHub
parent 5e091572d5
commit 69558d8889
31 changed files with 1826 additions and 24 deletions

View File

@@ -0,0 +1,21 @@
# WRL/X3DV to X3D file format converter
## VRML and X3D 3D model formats background
"VRML" 3D model files use either `VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`)
file formats.
The X3D model specification was introduced after these formats, as a superset of both WRL and X3DV.
While X3D can understand the _content_ of WRL/X3DV files, it can't directly parse them because
X3D uses `.xml` files, rather than `VRML97` or "Classic VRML" format.
But, if a converter is available to migrate just the file format (preserving the content), so that
the `.wrl`/`.x3dv` files can be converted to an X3D-compatible `.xml` file, then the X3D importer
will be able to load the resulting model file.
## How this code is used
The sole purpose of `Parser`/`Scanner` (adopted from the `meshlab` project) is to take a
`VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`) file as input, and convert to an X3D `.xml` file.
That's it.
By passing the converted in-memory `.xml` file content to the `X3DImporter`, the `.wrl` or `x3dv`
model can be loaded via assimp.

View File

@@ -0,0 +1,103 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2024, 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 VrmlImporter.cpp
/// \brief Convert VRML-formatted (.wrl, .x3dv) files to X3D .xml format
/// \date 2024
/// \author tellypresence
#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
#include <memory> // std::unique_ptr
#include "VrmlConverter.hpp"
namespace Assimp {
bool isFileWrlVrml97Ext(const std::string &pFile) {
size_t pos = pFile.find_last_of('.');
if (pos == std::string::npos) {
return false;
}
std::string ext = pFile.substr(pos + 1);
if (ext.size() != 3) {
return false;
}
return (ext[0] == 'w' || ext[0] == 'W') && (ext[1] == 'r' || ext[1] == 'R') && (ext[2] == 'l' || ext[2] == 'L');
}
bool isFileX3dvClassicVrmlExt(const std::string &pFile) {
size_t pos = pFile.find_last_of('.');
if (pos == std::string::npos) {
return false;
}
std::string ext = pFile.substr(pos + 1);
if (ext.size() != 4) {
return false;
}
return (ext[0] == 'x' || ext[0] == 'X') && (ext[1] == '3') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'v' || ext[3] == 'V');
}
#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
static VrmlTranslator::Scanner createScanner(const std::string &pFile) {
std::unique_ptr<wchar_t[]> wide_stringPtr{ new wchar_t[ pFile.length() + 1 ] };
std::copy(pFile.begin(), pFile.end(), wide_stringPtr.get());
wide_stringPtr[ pFile.length() ] = 0;
return VrmlTranslator::Scanner(wide_stringPtr.get());
} // wide_stringPtr auto-deleted when leaving scope
#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile) {
std::stringstream ss;
if (isFileWrlVrml97Ext(pFile) || isFileX3dvClassicVrmlExt(pFile)) {
#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
VrmlTranslator::Scanner scanner = createScanner(pFile);
VrmlTranslator::Parser parser(&scanner);
parser.Parse();
ss.str("");
parser.doc_.save(ss);
#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
}
return ss;
}
} // namespace Assimp
#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER

View File

@@ -0,0 +1,57 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2024, 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 <sstream>
#include <string>
#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
#include "contrib/meshlab/autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.h"
#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
namespace Assimp {
bool isFileWrlVrml97Ext(const std::string &pFile);
bool isFileX3dvClassicVrmlExt(const std::string &pFile);
std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile);
} // namespace Assimp

View File

@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
#include "AssetLib/VRML/VrmlConverter.hpp"
#include "X3DImporter.hpp"
#include "X3DImporter_Macro.hpp"
@@ -54,11 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <iterator>
#include <memory>
#if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
#define X3D_FORMATS_DESCR_STR "Extensible 3D(X3D, X3DB) Importer"
#define X3D_FORMATS_EXTENSIONS_STR "x3d x3db"
#else
#define X3D_FORMATS_DESCR_STR "VRML(WRL, X3DV) and Extensible 3D(X3D, X3DB) Importer"
#define X3D_FORMATS_EXTENSIONS_STR "wrl x3d x3db x3dv"
#endif // #if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
namespace Assimp {
/// Constant which holds the importer description
const aiImporterDesc X3DImporter::Description = {
"Extensible 3D(X3D) Importer",
X3D_FORMATS_DESCR_STR,
"smalcom",
"",
"See documentation in source code. Chapter: Limitations.",
@@ -67,7 +76,7 @@ const aiImporterDesc X3DImporter::Description = {
0,
0,
0,
"x3d x3db"
X3D_FORMATS_EXTENSIONS_STR
};
bool X3DImporter::isNodeEmpty(XmlNode &node) {
@@ -215,7 +224,19 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) {
if (!theParser.parse(fileStream.get())) {
return;
}
ParseFile(theParser);
}
void X3DImporter::ParseFile(std::istream &myIstream) {
XmlParser theParser;
if (!theParser.parse(myIstream)) {
LogInfo("ParseFile(): ERROR: failed to convert VRML istream to xml");
return;
}
ParseFile(theParser);
}
void X3DImporter::ParseFile(XmlParser &theParser) {
XmlNode *node = theParser.findNode("X3D");
if (nullptr == node) {
return;
@@ -246,9 +267,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
mpIOHandler = pIOHandler;
Clear();
std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
if (!stream) {
throw DeadlyImportError("Could not open file for reading");
std::stringstream ss = ConvertVrmlFileToX3dXmlFile(pFile);
const bool isReadFromMem{ ss.str().length() > 0 };
if (!isReadFromMem) {
std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
if (!stream) {
throw DeadlyImportError("Could not open file for reading");
}
}
std::string::size_type slashPos = pFile.find_last_of("\\/");
@@ -257,9 +282,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
pScene->mRootNode->mParent = nullptr;
pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
ParseFile(pFile, pIOHandler);
pIOHandler->PopDirectory();
if (isReadFromMem) {
ParseFile(ss);
} else {
pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
ParseFile(pFile, pIOHandler);
pIOHandler->PopDirectory();
}
//search for root node element

View File

@@ -275,7 +275,9 @@ public:
/// 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 ParseFile(const std::string &file, IOSystem *pIOHandler);
void ParseFile(std::istream &myIstream);
void ParseFile(XmlParser &theParser);
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const;
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
const aiImporterDesc *GetInfo() const;

View File

@@ -864,6 +864,7 @@ ADD_ASSIMP_IMPORTER( X3D
AssetLib/X3D/X3DGeoHelper.h
AssetLib/X3D/X3DXmlHelper.cpp
AssetLib/X3D/X3DXmlHelper.h
AssetLib/VRML/VrmlConverter.cpp
)
ADD_ASSIMP_IMPORTER( GLTF
@@ -930,6 +931,69 @@ SET( Extra_SRCS
)
SOURCE_GROUP( Extra FILES ${Extra_SRCS})
# VRML (.wrl/.x3dv) support
IF (ASSIMP_BUILD_VRML_IMPORTER)
# Note: ALWAYS specify a git commit hash (or tag) instead of a branch name; using a branch name
# can lead to non-deterministic (unpredictable) results since the code is potentially in flux
# "main" branch, 18 Nov 2024
set(Meshlab_GIT_TAG "ad55b47a9b0700e7b427db6db287bb3a39aa31e7")
message("****")
message("\n\n**** Cloning meshlab repo, git tag ${Meshlab_GIT_TAG}\n\n")
# Use CMAKE_CURRENT_SOURCE_DIR which provides assimp-local path (CMAKE_SOURCE_DIR is
# relative to top-level/main project)
set(Meshlab_BASE_ABSPATH "${CMAKE_CURRENT_SOURCE_DIR}/../contrib/meshlab")
# Note: Depending on user's OS, build environment etc it may be necessary to change line endings of
# "patches/meshlab.patch" file from CRLF to LF in order for patch operation to succeed
# Patch required to
# - replace QtXml w/pugixml
# - disable meshlab cmake scripts to prevent breaking assimp build
# - address compiler warnings to avoid breaking build for users who wisely treat warnings-as-errors
set(Meshlab_PATCH_CMD git apply ${Meshlab_BASE_ABSPATH}/patches/meshlab.patch)
# Note: cloning entire meshlab repo is wasteful since we require literally only two source files.
# There is a technique using "git archive" e.g.
# execute_process(
# COMMAND git archive --remote=${Meshlab_GIT_REPO} ${Meshlab_GIT_TAG} <files> | \
# tar -x -C ${Meshlab_BASE_ABSPATH}
# RESULT_VARIABLE result
# )
# But this doesn't work with git "https" protocol so not a viable solution
# Note: CMake's "FetchContent" (which executes at configure time) is much better for this use case
# than "ExternalProject" (which executes at build time); we just want to clone a repo and
# block (wait) as long as necessary until cloning is complete, so we immediately have full
# access to the cloned source files
include(FetchContent)
set(Meshlab_REPO_ABSPATH "${Meshlab_BASE_ABSPATH}/autoclone")
# Only want to clone once (on Android, using SOURCE_DIR will clone per-ABI (x86, x86_64 etc))
set(FETCHCONTENT_BASE_DIR ${Meshlab_REPO_ABSPATH})
set(FETCHCONTENT_QUIET on) # Turn off to troubleshoot repo clone problems
set(FETCHCONTENT_UPDATES_DISCONNECTED on) # Prevent other ABIs from re-cloning/re-patching etc
set(Meshlab_GIT_REPO "https://github.com/cnr-isti-vclab/meshlab")
FetchContent_Declare(
meshlab_repo
GIT_REPOSITORY ${Meshlab_GIT_REPO}
GIT_TAG ${Meshlab_GIT_TAG}
PATCH_COMMAND ${Meshlab_PATCH_CMD}
)
FetchContent_MakeAvailable(meshlab_repo)
message("**** Finished cloning meshlab repo")
message("****")
set(Meshlab_SRC_ABSPATH "${Meshlab_REPO_ABSPATH}/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml")
set(Meshlab_SRCS
${Meshlab_SRC_ABSPATH}/Parser.cpp
${Meshlab_SRC_ABSPATH}/Scanner.cpp
)
set(Meshlab_INCLUDE_DIRS "${Meshlab_SRC_ABSPATH}")
INCLUDE_DIRECTORIES(${Meshlab_INCLUDE_DIRS})
MESSAGE(STATUS "VRML enabled")
ELSE() # IF (ASSIMP_BUILD_VRML_IMPORTER)
set(Meshlab_SRCS "")
MESSAGE(STATUS "VRML disabled")
ENDIF() # IF (ASSIMP_BUILD_VRML_IMPORTER)
# USD/USDA/USDC/USDZ support
# tinyusdz
IF (ASSIMP_BUILD_USD_IMPORTER)
@@ -1297,6 +1361,7 @@ SET( assimp_src
${openddl_parser_SRCS}
${open3dgc_SRCS}
${ziplib_SRCS}
${Meshlab_SRCS}
${Tinyusdz_SRCS}
${Tinyusdz_DEP_SOURCES}
${Pugixml_SRCS}