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:
21
code/AssetLib/VRML/README.md
Normal file
21
code/AssetLib/VRML/README.md
Normal 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.
|
||||
103
code/AssetLib/VRML/VrmlConverter.cpp
Normal file
103
code/AssetLib/VRML/VrmlConverter.cpp
Normal 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
|
||||
57
code/AssetLib/VRML/VrmlConverter.hpp
Normal file
57
code/AssetLib/VRML/VrmlConverter.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user