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>
2
.gitignore
vendored
@@ -122,5 +122,7 @@ tools/assimp_qt_viewer/ui_mainwindow.h
|
||||
generated/*
|
||||
|
||||
# 3rd party cloned repos/tarballs etc
|
||||
# meshlab repo, automatically cloned via CMake (to gain 2 source files for VRML file format conversion)
|
||||
contrib/meshlab/autoclone
|
||||
# tinyusdz repo, automatically cloned via CMake
|
||||
contrib/tinyusdz/autoclone
|
||||
|
||||
@@ -40,12 +40,47 @@ 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)
|
||||
@@ -56,19 +91,6 @@ if(ASSIMP_BUILD_USE_CCACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# Toggles the use of the hunter package manager
|
||||
option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
|
||||
|
||||
|
||||
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
@@ -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
@@ -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}
|
||||
|
||||
11
contrib/meshlab/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# meshlab
|
||||
Meshlab project cloned in entirety but only using two files: "Parser" and "Scanner" in order to
|
||||
reformat .wrl/.x3dv files as .xml
|
||||
|
||||
## Automatic repo clone
|
||||
Meshlab repo is automatically cloned. Users who haven't opted-in to VRML support
|
||||
won't be burdened with the extra download volume.
|
||||
|
||||
To update the git commit hash pulled down, modify `Meshlab_GIT_TAG` in file
|
||||
`code/CMakeLists.txt`; it is not expected that the sole files of interest "Parser" and "Scanner"
|
||||
will change frequently, if at all, going forward
|
||||
14
contrib/meshlab/patches/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# meshlab patch
|
||||
|
||||
## Notes
|
||||
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
|
||||
|
||||
## Overview
|
||||
"Parser" based on QtXml, need to change to use pugixml
|
||||
|
||||
## pugixml notes
|
||||
Note that it isn't possible to add an unattached pugixml Node object, modify it, then
|
||||
add it to the tree later; the node needs to be attached somewhere in the tree on instantiation
|
||||
(even if it's a temporary at root level to be removed when finished) before populating, adding
|
||||
children etc.
|
||||
1191
contrib/meshlab/patches/meshlab.patch
Normal file
@@ -5,7 +5,7 @@
|
||||
tinyusdz repo is automatically cloned. Users who haven't opted-in to USD support
|
||||
won't be burdened with the extra download volume.
|
||||
|
||||
To update te git commit hash pulled down, modify `TINYUSDZ_GIT_TAG` in file
|
||||
To update the git commit hash pulled down, modify `TINYUSDZ_GIT_TAG` in file
|
||||
`code/CMakeLists.txt`
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "IOStream.hpp"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
#include <istream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -128,6 +129,11 @@ public:
|
||||
/// @return true, if the parsing was successful, false if not.
|
||||
bool parse(IOStream *stream);
|
||||
|
||||
/// @brief Will parse an xml-file from a stringstream.
|
||||
/// @param[in] str The input istream (note: not "const" to match pugixml param)
|
||||
/// @return true, if the parsing was successful, false if not.
|
||||
bool parse(std::istream &inStream);
|
||||
|
||||
/// @brief Will return true if a root node is there.
|
||||
/// @return true in case of an existing root.
|
||||
bool hasRoot() const;
|
||||
@@ -311,7 +317,23 @@ bool TXmlParser<TNodeType>::parse(IOStream *stream) {
|
||||
mDoc = new pugi::xml_document();
|
||||
// load_string assumes native encoding (aka always utf-8 per build options)
|
||||
//pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full);
|
||||
pugi::xml_parse_result parse_result = mDoc->load_buffer(&mData[0], mData.size(), pugi::parse_full);
|
||||
pugi::xml_parse_result parse_result = mDoc->load_buffer(&mData[0], mData.size(), pugi::parse_full);
|
||||
if (parse_result.status == pugi::status_ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class TNodeType>
|
||||
bool TXmlParser<TNodeType>::parse(std::istream &inStream) {
|
||||
if (hasRoot()) {
|
||||
clear();
|
||||
}
|
||||
mDoc = new pugi::xml_document();
|
||||
pugi::xml_parse_result parse_result = mDoc->load(inStream);
|
||||
if (parse_result.status == pugi::status_ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
87
test/models/WRL/HelloWorld.wrl
Normal file
@@ -0,0 +1,87 @@
|
||||
#VRML V2.0 utf8
|
||||
# X3D-to-VRML-97 XSL translation autogenerated by X3dToVrml97.xslt
|
||||
# https://www.web3d.org/x3d/content/X3dToVrml97.xslt
|
||||
# Generated using XSLT processor: Saxonica
|
||||
|
||||
# [X3D] VRML V3.3 utf8
|
||||
# PROFILE Immersive
|
||||
# [X3D] version=3.3
|
||||
# [X3D] noNamespaceSchemaLocation=https://www.web3d.org/specifications/x3d-3.3.xsd
|
||||
# [head]
|
||||
|
||||
# Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON
|
||||
# META "title" "HelloWorld.x3d"
|
||||
# META "description" "Simple X3D model example: Hello World!"
|
||||
# META "created" "30 October 2000"
|
||||
# META "modified" "9 July 2023"
|
||||
# META "creator" "Don Brutzman"
|
||||
# META "Image" "HelloWorld.tall.png"
|
||||
# META "reference" "https://en.wikipedia.org/wiki/Hello_world"
|
||||
# META "reference" "https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program"
|
||||
# META "reference" "https://en.wikipedia.org/wiki/\"Hello,_World!\"_program"
|
||||
# META "reference" "https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world"
|
||||
# META "reference" "https://www.HelloWorldExample.net"
|
||||
# META "reference" "https://www.web3d.org"
|
||||
# META "reference" "https://www.web3d.org/realtime-3d/news/internationalization-x3d"
|
||||
# META "reference" "https://www.web3d.org/x3d/content/examples/HelloWorld.x3d"
|
||||
# META "reference" "https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d"
|
||||
# META "identifier" "https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d"
|
||||
# META "license" "https://www.web3d.org/x3d/content/examples/license.html"
|
||||
# META "generator" "X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit"
|
||||
# META "reference" "HelloWorld.wrl"
|
||||
# META "reference" "HelloWorld.x3dv"
|
||||
# META "reference" "HelloWorld.x3db"
|
||||
# META "reference" "HelloWorld.xhtml"
|
||||
# META "reference" "HelloWorld.json"
|
||||
|
||||
# [Scene] ========== ========== ==========
|
||||
|
||||
NavigationInfo { type [ "EXAMINE" "ANY" ] } ### Default X3D NavigationInfo
|
||||
|
||||
# Example scene to illustrate X3D nodes and fields (XML elements and attributes)
|
||||
WorldInfo {
|
||||
info [ "Example scene to illustrate a simple X3D model" ]
|
||||
title "Hello World!"
|
||||
}
|
||||
Group {
|
||||
children [
|
||||
DEF ViewUpClose Viewpoint {
|
||||
centerOfRotation 0 -1 0
|
||||
description "Hello world!"
|
||||
position 0 -1 7
|
||||
}
|
||||
Transform {
|
||||
rotation 0 1 0 3
|
||||
children [
|
||||
Shape {
|
||||
geometry Sphere {
|
||||
}
|
||||
appearance Appearance {
|
||||
material DEF MaterialOffWhite Material {
|
||||
diffuseColor 0.980392 0.976471 0.964706
|
||||
}
|
||||
texture DEF ImageCloudlessEarth ImageTexture {
|
||||
url [ "earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Transform {
|
||||
translation 0 -2 0
|
||||
children [
|
||||
Shape {
|
||||
geometry DEF TextMessage Text {
|
||||
string [ "Hello" "world!" ]
|
||||
fontStyle FontStyle {
|
||||
justify [ "MIDDLE" "MIDDLE" ]
|
||||
}
|
||||
}
|
||||
appearance Appearance {
|
||||
material USE MaterialOffWhite
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
6
test/models/WRL/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# WRL models
|
||||
|
||||
X3D is a modern superset of the old (VRML) WRL format
|
||||
|
||||
# HelloWorld.wrl
|
||||
Downloaded from [HelloWorld.wrl](http://www.web3d.org/x3d/content/examples/HelloWorld.wrl)
|
||||
BIN
test/models/WRL/earth-topo.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
4
test/models/WRL/ref/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# WRL 3D model reference images
|
||||
|
||||
## HelloWorld.wrl
|
||||
<img alt="HelloWorld.wrl" src="screenshots/HelloWorld_wrl.png" width=180 />
|
||||
BIN
test/models/WRL/ref/screenshots/HelloWorld_wrl.png
Normal file
|
After Width: | Height: | Size: 802 KiB |
57
test/models/X3D/HelloWorld.x3d
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.3//EN" "https://www.web3d.org/specifications/x3d-3.3.dtd">
|
||||
<X3D profile='Immersive' version='3.3' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-3.3.xsd'>
|
||||
<head>
|
||||
<!-- Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON -->
|
||||
<meta content='HelloWorld.x3d' name='title'/>
|
||||
<meta content='Simple X3D model example: Hello World!' name='description'/>
|
||||
<meta content='30 October 2000' name='created'/>
|
||||
<meta content='9 July 2023' name='modified'/>
|
||||
<meta content='Don Brutzman' name='creator'/>
|
||||
<meta content='HelloWorld.tall.png' name='Image'/>
|
||||
<meta content='https://en.wikipedia.org/wiki/Hello_world' name='reference'/>
|
||||
<meta content='https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program' name='reference'/>
|
||||
<meta content='https://en.wikipedia.org/wiki/"Hello,_World!"_program' name='reference'/>
|
||||
<meta content='https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world' name='reference'/>
|
||||
<meta content='https://www.HelloWorldExample.net' name='reference'/>
|
||||
<meta content='https://www.web3d.org' name='reference'/>
|
||||
<meta content='https://www.web3d.org/realtime-3d/news/internationalization-x3d' name='reference'/>
|
||||
<meta content='https://www.web3d.org/x3d/content/examples/HelloWorld.x3d' name='reference'/>
|
||||
<meta content='https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d' name='reference'/>
|
||||
<meta content='https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d' name='identifier'/>
|
||||
<meta content='https://www.web3d.org/x3d/content/examples/license.html' name='license'/>
|
||||
<meta content='X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit' name='generator'/>
|
||||
<meta content='HelloWorld.wrl' name='reference'/>
|
||||
<meta content='HelloWorld.x3dv' name='reference'/>
|
||||
<meta content='HelloWorld.x3db' name='reference'/>
|
||||
<meta content='HelloWorld.xhtml' name='reference'/>
|
||||
<meta content='HelloWorld.json' name='reference'/>
|
||||
</head>
|
||||
<Scene>
|
||||
<!-- Example scene to illustrate X3D nodes and fields (XML elements and attributes) -->
|
||||
<WorldInfo info='"Example scene to illustrate a simple X3D model"' title='Hello World!'/>
|
||||
<Group>
|
||||
<Viewpoint DEF='ViewUpClose' centerOfRotation='0 -1 0' description='Hello world!' position='0 -1 7'/>
|
||||
<Transform rotation='0 1 0 3'>
|
||||
<Shape>
|
||||
<Sphere/>
|
||||
<Appearance>
|
||||
<!-- https://htmlcolorcodes.com/colors/off-white -->
|
||||
<Material DEF='MaterialOffWhite' diffuseColor='0.980392 0.976471 0.964706'/>
|
||||
<ImageTexture DEF='ImageCloudlessEarth' url='"earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif"'/>
|
||||
</Appearance>
|
||||
</Shape>
|
||||
</Transform>
|
||||
<Transform translation='0 -2 0'>
|
||||
<Shape>
|
||||
<Text DEF='TextMessage' string='"Hello" "world!"'>
|
||||
<FontStyle justify='"MIDDLE" "MIDDLE"'/>
|
||||
</Text>
|
||||
<Appearance>
|
||||
<Material USE='MaterialOffWhite'/>
|
||||
</Appearance>
|
||||
</Shape>
|
||||
</Transform>
|
||||
</Group>
|
||||
</Scene>
|
||||
</X3D>
|
||||
4
test/models/X3D/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# X3D models
|
||||
|
||||
# HelloWorld.x3d
|
||||
Downloaded from [HelloWorld.x3d](http://www.web3d.org/x3d/content/examples/HelloWorld.x3d)
|
||||
BIN
test/models/X3D/earth-topo.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
@@ -12,3 +12,6 @@ ComputerKeyboard as of 1 Dec 2021 git commit `1614934`, using pugi xml parsing,
|
||||
missing meshes and obvious artifacts:
|
||||
|
||||
<img alt="ComputerKeyboard.x3d (pugi xml)" src="screenshots/ComputerKeyboard_x3d_pugi_xml.png" width=320 />
|
||||
|
||||
## HelloWorld.x3d
|
||||
<img alt="HelloWorld.x3d" src="screenshots/HelloWorld_x3d.png" width=180 />
|
||||
|
||||
BIN
test/models/X3D/ref/screenshots/HelloWorld_x3d.png
Normal file
|
After Width: | Height: | Size: 929 KiB |
BIN
test/models/X3DB/HelloWorld.x3db
Normal file
6
test/models/X3DB/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# X3DB models
|
||||
|
||||
X3DB models are compressed X3D models
|
||||
|
||||
# HelloWorld.x3db
|
||||
Downloaded from [HelloWorld.x3db](http://www.web3d.org/x3d/content/examples/HelloWorld.x3db)
|
||||
BIN
test/models/X3DB/earth-topo.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
86
test/models/X3DV/HelloWorld.x3dv
Normal file
@@ -0,0 +1,86 @@
|
||||
#X3D V3.3 utf8
|
||||
# X3D-to-ClassicVRML XSL translation autogenerated by X3dToVrml97.xslt
|
||||
# https://www.web3d.org/x3d/content/X3dToVrml97.xslt
|
||||
# Generated using XSLT processor: Saxonica
|
||||
|
||||
PROFILE Immersive
|
||||
# [X3D] version=3.3
|
||||
# [X3D] noNamespaceSchemaLocation=https://www.web3d.org/specifications/x3d-3.3.xsd
|
||||
# [head]
|
||||
|
||||
# Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON
|
||||
META "title" "HelloWorld.x3d"
|
||||
META "description" "Simple X3D model example: Hello World!"
|
||||
META "created" "30 October 2000"
|
||||
META "modified" "9 July 2023"
|
||||
META "creator" "Don Brutzman"
|
||||
META "Image" "HelloWorld.tall.png"
|
||||
META "reference" "https://en.wikipedia.org/wiki/Hello_world"
|
||||
META "reference" "https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program"
|
||||
META "reference" "https://en.wikipedia.org/wiki/\"Hello,_World!\"_program"
|
||||
META "reference" "https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world"
|
||||
META "reference" "https://www.HelloWorldExample.net"
|
||||
META "reference" "https://www.web3d.org"
|
||||
META "reference" "https://www.web3d.org/realtime-3d/news/internationalization-x3d"
|
||||
META "reference" "https://www.web3d.org/x3d/content/examples/HelloWorld.x3d"
|
||||
META "reference" "https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d"
|
||||
META "identifier" "https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d"
|
||||
META "license" "https://www.web3d.org/x3d/content/examples/license.html"
|
||||
META "generator" "X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit"
|
||||
META "reference" "HelloWorld.wrl"
|
||||
META "reference" "HelloWorld.x3dv"
|
||||
META "reference" "HelloWorld.x3db"
|
||||
META "reference" "HelloWorld.xhtml"
|
||||
META "reference" "HelloWorld.json"
|
||||
|
||||
# [Scene] ========== ========== ==========
|
||||
|
||||
NavigationInfo { type [ "EXAMINE" "ANY" ] } ### Default X3D NavigationInfo
|
||||
|
||||
# Example scene to illustrate X3D nodes and fields (XML elements and attributes)
|
||||
WorldInfo {
|
||||
info [ "Example scene to illustrate a simple X3D model" ]
|
||||
title "Hello World!"
|
||||
}
|
||||
Group {
|
||||
children [
|
||||
DEF ViewUpClose Viewpoint {
|
||||
centerOfRotation 0 -1 0
|
||||
description "Hello world!"
|
||||
position 0 -1 7
|
||||
}
|
||||
Transform {
|
||||
rotation 0 1 0 3
|
||||
children [
|
||||
Shape {
|
||||
geometry Sphere {
|
||||
}
|
||||
appearance Appearance {
|
||||
material DEF MaterialOffWhite Material {
|
||||
diffuseColor 0.980392 0.976471 0.964706
|
||||
}
|
||||
texture DEF ImageCloudlessEarth ImageTexture {
|
||||
url [ "earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Transform {
|
||||
translation 0 -2 0
|
||||
children [
|
||||
Shape {
|
||||
geometry DEF TextMessage Text {
|
||||
string [ "Hello" "world!" ]
|
||||
fontStyle FontStyle {
|
||||
justify [ "MIDDLE" "MIDDLE" ]
|
||||
}
|
||||
}
|
||||
appearance Appearance {
|
||||
material USE MaterialOffWhite
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
6
test/models/X3DV/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# X3DV models
|
||||
|
||||
X3DV models are as simple WRL (VRML) models with the version number upgraded from `2` to `3`
|
||||
|
||||
# HelloWorld.x3dv
|
||||
Downloaded from [HelloWorld.x3dv](http://www.web3d.org/x3d/content/examples/HelloWorld.x3dv)
|
||||
BIN
test/models/X3DV/earth-topo.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
4
test/models/X3DV/ref/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# X3DV 3D model reference images
|
||||
|
||||
## HelloWorld.x3dv
|
||||
<img alt="HelloWorld.x3dv" src="screenshots/HelloWorld_x3dv.png" width=180 />
|
||||
BIN
test/models/X3DV/ref/screenshots/HelloWorld_x3dv.png
Normal file
|
After Width: | Height: | Size: 918 KiB |