Auto-load nearby texture. Simplify&test cycling functions.

master
poikilos 2020-03-10 14:12:16 -04:00
parent e5a8ed0204
commit 460690fded
9 changed files with 577 additions and 309 deletions

View File

@ -1,170 +1,225 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [git] - 2019-07-03
(poikilos)
### Added
- `replaceAll`
- `TestUtility` ("Utility.cpp" now tests itself, but only one feature
so far.)
- `getTextureCount` (can examine node by examining all materials, or
examine material
- Blitz3D format notes
- `OnSelectMesh` (cleans up model-specific variables)
- `getSuffix`
- `getPrefix`
- `startsWithAny`
- `endsWithAny`
### Changed
- Rename some member variables to start with `m_`.
- Detect textures better, and simplify code:
- Search for substring and substring without underscores within
potential textures.
- Detect as soon as model is loaded if the model has no textures.
- Cache both the full and matching texture lists.
- If there are any matching textures (named like model), only use that
list for cycling with F3 (or Shift F3).
- Combine `m_NextPath` and `m_PreviousPath` into `m_LoadedMeshPath`
and change usage.
- Update screenshot.
- Reduce line length in some places.
- Improve Changelog formatting.
### Fixed
- Fix crash trying to load a non-mesh after a mesh was loaded
(See "Manipulating mesh on failed load" section of README.md).
### Removed
- `m_NextPath` and `m_PreviousPath` (replaced by `m_LoadedMeshPath` and
simplified cycling code)
## [git] - 2019-07-03 ## [git] - 2019-07-03
(poikilos) (poikilos)
### Changes ### Changes
* Move display mode booleans to engine. - Move the display mode booleans to Engine.
* Add more string utilities. - Add more string utilities.
* Do fuzzy search against actual texture names if can't find theoretical - Do fuzzy search against actual texture names if can't find theoretical
ones. ones.
## [git] - 2019-05-16 ## [git] - 2019-05-16
(poikilos) (poikilos)
### Added ### Added
* playback menu - playback menu
- Move framerate controls to playback menu. - Move framerate controls to playback menu.
* fix frame-by-frame hotkeys - fix frame-by-frame hotkeys
- move code to new incrementFrame method - move code to new incrementFrame method
# Changelog
## [git] - 2019-05-16 ## [git] - 2019-05-16
(poikilos) (poikilos)
### Changed ### Changed
* improve minetest texture detection (alternate conventions) - improve minetest texture detection (alternate conventions)
* turn off interpolation if loadNextTexture detects minetest directory - turn off interpolation if loadNextTexture detects minetest directory
structure (../textures/<texture filename based on model name>) structure (../textures/<texture filename based on model name>)
## [git] - 2019-05-16 ## [git] - 2019-05-16
(poikilos) (poikilos)
### Added ### Added
* export COLLADA (non-Blender), IRR, IRRMESH, OBJ, STL - export COLLADA (non-Blender), IRR, IRRMESH, OBJ, STL
* show dialog box if operation can't be performed - show dialog box if operation can't be performed
- improve error reporting in called methods - improve error reporting in called methods
* add irr mimetype (Irrlicht Scene, mesh file references and settings - add irr mimetype (Irrlicht Scene, mesh file references and settings
only) only)
* add irrlicht mimetype (static/non-animated Irrlicht mesh) - add irrlicht mimetype (static/non-animated Irrlicht mesh)
## [git] - 2019-04-19 ## [git] - 2019-04-19
(poikilos) (poikilos)
### Added ### Added
* box for axis length (size of the axis widget) - box for axis length (size of the axis widget)
* box for frame rate - box for frame rate
* camera target widget - camera target widget
* option for turning off origin axis widget - option for turning off origin axis widget
* Add menu items for hotkeys, and show hotkey on relevant menu items. - Add menu items for hotkeys, and show hotkey on relevant menu items.
### Changed ### Changed
* Reorder items on panel. - Reorder items on panel.
* Hotkeys are different so they're not triggered when typing in the - Hotkeys are different so they're not triggered when typing in the
panel. panel.
* Don't reset yaw nor camera distance when panning. - Don't reset yaw nor camera distance when panning.
* Show name of loaded model on title bar. - Show name of loaded model on title bar.
* Fix crash on loading texture before model. - Fix crash on loading texture before model.
* Fix use of unsigned frame delta for slow and fast options. - Fix use of unsigned frame delta for slow and fast options.
## [git] - 2019-04-08 ## [git] - 2019-04-08
(poikilos) (poikilos)
### Added ### Added
* snapWidgets (move playbackWindow on resize, not leave past edge) - snapWidgets (move playbackWindow on resize, not leave past edge)
### Changed ### Changed
* changed enum values to leave room in between, comment unused - changed enum values to leave room in between, comment unused
* fixed issue in Utility not detecting backslashes correctly - fixed issue in Utility not detecting backslashes correctly
* renamed Utils.* to Utility.* to match class name - renamed Utils.* to Utility.* to match class name
* coding style to WebKit (run ./etc/quality.sh to check) - coding style to WebKit (run ./etc/quality.sh to check)
* improve pan - don't reset view - improve pan - don't reset view
* improve initial camera settings: angle calculation - improve initial camera settings: angle calculation
## [git] - 2019-04-08 ## [git] - 2019-04-08
(poikilos) (poikilos)
### Added ### Added
* toggle texture interpolation (via checkbox and `x` hotkey) - toggle texture interpolation (via checkbox and `x` hotkey)
* INDEX_ variables to store ID of GUI elements - INDEX_ variables to store ID of GUI elements
* Text box show name of loaded texture path - Text box show name of loaded texture path
### Changed ### Changed
* check if model is loaded before changing view options (prevents crash) - check if model is loaded before changing view options (prevents crash)
* unified checkboxes with m_* booleans, by tracking whether box is - unified checkboxes with m_* booleans, by tracking whether box is
checked via INDEX_ variables for each ID of GUI elements. checked via INDEX_ variables for each ID of GUI elements.
* look for ../textures/<model basename>.png & .jpg 1st time pressing `t` - look for ../textures/<model basename>.png & .jpg 1st time pressing `t`
* Use alpha on textures by default - Use alpha on textures by default
(see EMT_TRANSPARENT_ALPHA_CHANNEL_REF in Engine.cpp) (see EMT_TRANSPARENT_ALPHA_CHANNEL_REF in Engine.cpp)
## [git] - 2019-03-09
(poikilos)
### Added
* completed rotation controls (Blender-like)
* pan up and down (Blender-like, but only up and down)
* Z or Y to switch ("up" axis)
* change up axis to Z when 3ds is loaded
* model-ms3d.xml mime type file
## [git] - 2019-03-09 ## [git] - 2019-03-09
(poikilos) (poikilos)
### Added ### Added
* hotkeys to reload model/texture - completed rotation controls (Blender-like)
* license (see README.md for licensing history) - pan up and down (Blender-like, but only up and down)
- Z or Y to switch ("up" axis)
- change up axis to Z when 3ds is loaded
- model-ms3d.xml mime type file
## [git] - 2019-03-09
(poikilos)
### Added
- hotkeys to reload model/texture
- license (see README.md for licensing history)
### Changed ### Changed
* only try to load png or jpg textures--skip others when cycling - only try to load png or jpg textures--skip others when cycling
* cycle backwards correctly - cycle backwards correctly
* fix some of the header creep (remove unecessary includes in h files) - fix some of the header creep (remove unecessary includes in h files)
* improve initial camera position and angle (see top of characters since - improve initial camera position and angle (see top of characters since
camera is higher; z-forward characters face camera at an angle) camera is higher; z-forward characters face camera at an angle)
* Clarify relationship between camera start position in m_Engine and - Clarify relationship between camera start position in m_Engine and
m_View's rotation (m_Pitch and m_Yaw). Now, `setNewCameraPosition` m_View's rotation (m_Pitch and m_Yaw). Now, `setNewCameraPosition`
operates on view correctly (relatively) no matter where camera starts. operates on view correctly (relatively) no matter where camera starts.
## [git] - 2019-03-07 ## [git] - 2019-03-07
(poikilos) (poikilos)
### Added ### Added
* playback controls - playback controls
## [git] - 2019-03-06 ## [git] - 2019-03-06
(poikilos) (poikilos)
### Added ### Added
* created install.sh and install.bat, and added Install and Usage - created install.sh and install.bat, and added Install and Usage
to README.md to README.md
* icon, install scripts, and mime type (`model/b3d`)--see README.md - icon, install scripts, and mime type (`model/b3d`)--see README.md
* mime type (`model/x`) - mime type (`model/x`)
* added ClearSansRegular.ttf - added ClearSansRegular.ttf
* hotkeys to cycle ../textures/* - hotkeys to cycle ../textures/*
### Changed ### Changed
* The program can now start without "test.b3d" in the current working - The program can now start without "test.b3d" in the current working
directory (fixed Segmentation Fault). directory (fixed Segmentation Fault).
* set `TARGET = b3view` in B3View.pro, so that binary is lowercase as - set `TARGET = b3view` in B3View.pro, so that binary is lowercase as
per usual Linux naming conventions. per usual Linux naming conventions.
* check for font load failure properly, and load properly if succeeds - check for font load failure properly, and load properly if succeeds
* check for "ClearSansRegular.ttf" instead of "arial.ttf" - check for "ClearSansRegular.ttf" instead of "arial.ttf"
* move `using namespace` directives from `h` files and specify upon use, - move `using namespace` directives from `h` files and specify upon use,
as per C++ best practices; add directives to `cpp` files only as as per C++ best practices; add directives to `cpp` files only as
needed (removed cumulative namespace creep). needed (removed cumulative namespace creep).
## [git-94e3b8f] - 2019-03-06 ## [git-94e3b8f] - 2019-03-06
(poikilos) (poikilos)
### Added ### Added
* README.md - README.md
### Changed ### Changed
(CGUITTFont methods are in CGUITTFont class unless otherwise specified) (CGUITTFont methods are in CGUITTFont class unless otherwise specified)
* fixed instances of "0 as null pointer constant" (changed to `nullptr`) - fixed instances of "0 as null pointer constant" (changed to `nullptr`)
* changed inconsistent use of spaces and tabs (changed tabs to 4 spaces) - changed inconsistent use of spaces and tabs (changed tabs to 4 spaces)
* (UserInterface.cpp) fixed "logical not is only applied to the left - (UserInterface.cpp) fixed "logical not is only applied to the left
hand side of this comparison..." (put parenthesis around hand side of this comparison..." (put parenthesis around
`event.EventType == EET_GUI_EVENT`) `event.EventType == EET_GUI_EVENT`)
* Silently degrade to pixel font if font file cannot be read (fixes - Silently degrade to pixel font if font file cannot be read (fixes
Segmentation Fault when font file cannot be read). Segmentation Fault when font file cannot be read).
* check for nullptr before using: - check for nullptr before using:
* (CGUITTFont.cpp) `tt_face->face` in `getWidthFromCharacter`, - (CGUITTFont.cpp) `tt_face->face` in `getWidthFromCharacter`,
`getGlyphByChar` (return 0 as bad as per convention: `getGlyphByChar` (return 0 as bad as per convention:
existing code already checks for 0--see existing code already checks for 0--see
`getWidthFromCharacter`), `getKerningWidth`, `getWidthFromCharacter`), `getKerningWidth`,
`draw`, `attach` (also don't copy null by `draw`, `attach` (also don't copy null by
reference there--instead, set to nullptr if source is nullptr) reference there--instead, set to nullptr if source is nullptr)
* check length of array before using - check length of array before using
* (CGUITTFont.cpp) elements of `Glyph` array (type - (CGUITTFont.cpp) elements of `Glyph` array (type
`core::array<CGUITTGlyph>`) in `getHeightFromCharacter` `core::array<CGUITTGlyph>`) in `getHeightFromCharacter`
* (CGUITTFont.cpp) check whether file can be read in - (CGUITTFont.cpp) check whether file can be read in
`CGUITTFace::load` before proceeding `CGUITTFace::load` before proceeding
### Removed ### Removed
* arial.tff removed, since it may be the "real" Arial font, which has a - arial.tff removed, since it may be the "real" Arial font, which has a
proprietary license proprietary license
## [git-d964384] - 2019-03-06 ## [git-d964384] - 2019-03-06
### Changed ### Changed
(first poikilos commit, based on https://github.com/egrath) (first poikilos commit, based on https://github.com/egrath)
* changed `#include <irrlicht.h>` to `#include <irrlicht/irrlicht.h>` - changed `#include <irrlicht.h>` to `#include <irrlicht/irrlicht.h>`
### Added ### Added
* .gitignore (a [Qt .gitignore](https://github.com/github/gitignore/blob/master/Qt.gitignore)) - .gitignore (a [Qt .gitignore](https://github.com/github/gitignore/blob/master/Qt.gitignore))
* CHANGELOG.md - CHANGELOG.md

View File

@ -89,8 +89,10 @@ void Engine::setupScene()
// further down. // further down.
ICameraSceneNode* camera = m_Scene->addCameraSceneNode(nullptr, m_CamPos, ICameraSceneNode* camera = m_Scene->addCameraSceneNode(nullptr, m_CamPos,
m_CamTarget); m_CamTarget);
camera->setAspectRatio(static_cast<f32>(m_Driver->getScreenSize().Width) camera->setAspectRatio(
/ static_cast<f32>(m_Driver->getScreenSize().Height)); static_cast<f32>(m_Driver->getScreenSize().Width)
/ static_cast<f32>(m_Driver->getScreenSize().Height)
);
} }
IGUIEnvironment* Engine::getGUIEnvironment() const IGUIEnvironment* Engine::getGUIEnvironment() const
@ -189,10 +191,10 @@ void Engine::drawAxisLines()
if (enableAxisWidget) { if (enableAxisWidget) {
m_Driver->setMaterial(xMaterial); m_Driver->setMaterial(xMaterial);
m_Driver->draw3DLine(vector3df(), vector3df(axisLength, 0, 0), m_Driver->draw3DLine(vector3df(), vector3df(m_AxisLength, 0, 0),
SColor(255, 255, 0, 0)); SColor(255, 255, 0, 0));
position2d<s32> textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition( position2d<s32> textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition(
vector3df(axisLength + axisLength*.1f, 0, 0) vector3df(m_AxisLength + m_AxisLength*.1f, 0, 0)
); );
dimension2d<u32> textSize; dimension2d<u32> textSize;
if (m_AxisFont != nullptr) { if (m_AxisFont != nullptr) {
@ -202,10 +204,10 @@ void Engine::drawAxisLines()
} }
m_Driver->setMaterial(yMaterial); m_Driver->setMaterial(yMaterial);
m_Driver->draw3DLine(vector3df(), vector3df(0, axisLength, 0), m_Driver->draw3DLine(vector3df(), vector3df(0, m_AxisLength, 0),
SColor(255, 0, 255, 0)); SColor(255, 0, 255, 0));
textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition( textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition(
vector3df(0, axisLength + axisLength*.1f, 0) vector3df(0, m_AxisLength + m_AxisLength*.1f, 0)
); );
if (m_AxisFont != nullptr) { if (m_AxisFont != nullptr) {
textSize = m_AxisFont->getDimension(L"Y+"); textSize = m_AxisFont->getDimension(L"Y+");
@ -214,10 +216,10 @@ void Engine::drawAxisLines()
} }
m_Driver->setMaterial(zMaterial); m_Driver->setMaterial(zMaterial);
m_Driver->draw3DLine(vector3df(), vector3df(0, 0, axisLength), m_Driver->draw3DLine(vector3df(), vector3df(0, 0, m_AxisLength),
SColor(255, 0, 0, 255)); SColor(255, 0, 0, 255));
textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition( textPos = m_Scene->getSceneCollisionManager()->getScreenCoordinatesFrom3DPosition(
vector3df(0, 0, axisLength + axisLength*.1f) vector3df(0, 0, m_AxisLength + m_AxisLength*.1f)
); );
if (m_AxisFont != nullptr) { if (m_AxisFont != nullptr) {
textSize = m_AxisFont->getDimension(L"Z+"); textSize = m_AxisFont->getDimension(L"Z+");
@ -288,12 +290,12 @@ Engine::Engine()
this->m_EnableWireframe = false; this->m_EnableWireframe = false;
this->m_EnableLighting = false; this->m_EnableLighting = false;
this->m_EnableTextureInterpolation = true; this->m_EnableTextureInterpolation = true;
this->axisLength = 10; this->m_AxisLength = 10;
this->worldFPS = 60; this->m_WorldFPS = 60;
this->prevFPS = 30; this->m_PrevFPS = 30;
this->textureExtensions.push_back(L"png"); this->m_TextureExtensions.push_back(L"png");
this->textureExtensions.push_back(L"jpg"); this->m_TextureExtensions.push_back(L"jpg");
this->textureExtensions.push_back(L"bmp"); this->m_TextureExtensions.push_back(L"bmp");
#if WIN32 #if WIN32
m_Device = createDevice(EDT_DIRECT3D9, dimension2d<u32>(1024, 768), 32, m_Device = createDevice(EDT_DIRECT3D9, dimension2d<u32>(1024, 768), 32,
false, false, false, nullptr); false, false, false, nullptr);
@ -364,23 +366,28 @@ vector3df Engine::camTarget()
bool Engine::loadMesh(const wstring& fileName) bool Engine::loadMesh(const wstring& fileName)
{ {
bool ret = false; bool ret = false;
this->m_PreviousPath = fileName; // even if bad, set this
// to allow F5 to reload
if (m_LoadedMesh != nullptr)
m_LoadedMesh->remove();
irr::scene::IAnimatedMesh* mesh = m_Scene->getMesh(fileName.c_str()); irr::scene::IAnimatedMesh* mesh = m_Scene->getMesh(fileName.c_str());
if (mesh != nullptr) { if (mesh != nullptr) {
this->m_LoadedTexturePath = L"";
this->m_LoadedMeshPath = fileName; // even if bad, set this
// to allow F5 to reload
if (m_LoadedMesh != nullptr)
m_LoadedMesh->remove();
this->m_LoadedMesh = nullptr;
m_Device->setWindowCaption((wstring(L"b3view - ") + fileName).c_str()); m_Device->setWindowCaption((wstring(L"b3view - ") + fileName).c_str());
m_LoadedMesh = m_Scene->addAnimatedMeshSceneNode(mesh); m_LoadedMesh = m_Scene->addAnimatedMeshSceneNode(mesh);
Utility::dumpMeshInfoToConsole(m_LoadedMesh); Utility::dumpMeshInfoToConsole(m_LoadedMesh);
std::cerr << "Arranging scene..." << std::flush;
if (Utility::toLower(Utility::extensionOf(fileName)) == L"3ds") { if (Utility::toLower(Utility::extensionOf(fileName)) == L"3ds") {
m_View->setZUp(true); m_View->setZUp(true);
} else { } else {
m_View->setZUp(false); m_View->setZUp(false);
} }
if (m_LoadedMesh != nullptr) { if (m_LoadedMesh != nullptr) {
std::cerr << "unloading old mesh..." << std::flush;
ret = true; ret = true;
this->m_UserInterface->playbackFPSEditBox->setText( this->m_UserInterface->playbackFPSEditBox->setText(
Utility::toWstring(m_LoadedMesh->getAnimationSpeed()).c_str() Utility::toWstring(m_LoadedMesh->getAnimationSpeed()).c_str()
@ -431,19 +438,38 @@ bool Engine::loadMesh(const wstring& fileName)
video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF
); );
// EMT_TRANSPARENT_ALPHA_CHANNEL: constant transparency // EMT_TRANSPARENT_ALPHA_CHANNEL: constant transparency
} }
std::cerr << "setting display mode..." << std::flush;
this->setMeshDisplayMode(this->m_EnableWireframe, this->m_EnableLighting,
this->m_EnableTextureInterpolation);
std::cerr << "preparing UI..." << std::flush;
if (this->m_UserInterface != nullptr)
this->m_UserInterface->OnSelectMesh();
std::cerr << "checking for textures..." << std::flush;
std::cerr << "OK" << std::endl;
if (Utility::getTextureCount(m_LoadedMesh) == 0) {
// NOTE: getMaterialCount doesn't work, since there may not
// be loaded textures in any material.
if (this->m_UserInterface != nullptr) {
this->m_UserInterface->loadNextTexture(0);
}
}
} }
this->setMeshDisplayMode(this->m_EnableWireframe, this->m_EnableLighting, // Don't do anything outside of the mesh != nullptr case that will try to
this->m_EnableTextureInterpolation); // use mesh!
return ret; return ret;
} }
bool Engine::reloadMesh() bool Engine::reloadMesh()
{ {
bool ret = false; bool ret = false;
if (this->m_PreviousPath.length() > 0) { if (this->m_LoadedMeshPath.length() > 0) {
ret = loadMesh(this->m_PreviousPath); ret = loadMesh(this->m_LoadedMeshPath);
} }
if (this->m_UserInterface != nullptr)
this->m_UserInterface->OnSelectMesh();
return ret; return ret;
} }
@ -547,11 +573,11 @@ std::wstring Engine::saveMesh(const io::path path, const std::string& nameOrBlan
void Engine::reloadTexture() void Engine::reloadTexture()
{ {
if (this->m_PrevTexturePath.length() > 0) { if (this->m_LoadedTexturePath.length() > 0) {
if (wcslen(this->m_UserInterface->texturePathEditBox->getText()) == 0) if (wcslen(this->m_UserInterface->texturePathEditBox->getText()) == 0)
loadTexture(this->m_UserInterface->texturePathEditBox->getText()); loadTexture(this->m_UserInterface->texturePathEditBox->getText());
else else
loadTexture(this->m_PrevTexturePath); loadTexture(this->m_LoadedTexturePath);
} }
} }
@ -564,11 +590,15 @@ bool Engine::loadTexture(const wstring& fileName)
m_LoadedMesh->setMaterialTexture(0, texture); m_LoadedMesh->setMaterialTexture(0, texture);
ret = true; ret = true;
} }
this->m_PrevTexturePath = fileName; this->m_LoadedTexturePath = fileName;
std::cerr << "Setting texture path box to " << Utility::toString(this->m_LoadedTexturePath) << std::endl;
this->m_UserInterface->texturePathEditBox->setText( this->m_UserInterface->texturePathEditBox->setText(
this->m_PrevTexturePath.c_str() this->m_LoadedTexturePath.c_str()
); );
} }
else {
std::cerr << "NOT Setting texture path box to " << Utility::toString(this->m_LoadedTexturePath) << std::endl;
}
return ret; return ret;
} }
@ -638,7 +668,7 @@ void Engine::setMeshDisplayMode(bool wireframe, bool lighting,
bool Engine::isAnimating() bool Engine::isAnimating()
{ {
return this->isPlaying; return this->m_IsPlaying;
} }
void Engine::playAnimation() void Engine::playAnimation()
@ -648,24 +678,24 @@ void Engine::playAnimation()
} }
if (!this->isAnimating()) { if (!this->isAnimating()) {
if (this->m_LoadedMesh != nullptr) { if (this->m_LoadedMesh != nullptr) {
if (this->prevFPS < 1) if (this->m_PrevFPS < 1)
this->prevFPS = 5; this->m_PrevFPS = 5;
this->m_LoadedMesh->setAnimationSpeed(this->prevFPS); this->m_LoadedMesh->setAnimationSpeed(this->m_PrevFPS);
} }
} }
this->isPlaying = true; this->m_IsPlaying = true;
} }
void Engine::pauseAnimation() void Engine::pauseAnimation()
{ {
if (this->isAnimating()) { if (this->isAnimating()) {
this->prevFPS = animationFPS(); this->m_PrevFPS = animationFPS();
if (this->m_LoadedMesh != nullptr) { if (this->m_LoadedMesh != nullptr) {
this->prevFPS = this->m_LoadedMesh->getAnimationSpeed(); this->m_PrevFPS = this->m_LoadedMesh->getAnimationSpeed();
this->m_LoadedMesh->setAnimationSpeed(0); this->m_LoadedMesh->setAnimationSpeed(0);
} }
} }
this->isPlaying = false; this->m_IsPlaying = false;
} }
void Engine::toggleAnimation() void Engine::toggleAnimation()
@ -682,7 +712,7 @@ void Engine::toggleAnimation()
void Engine::setAnimationFPS(u32 animationFPS) void Engine::setAnimationFPS(u32 animationFPS)
{ {
if (this->m_LoadedMesh != nullptr) { if (this->m_LoadedMesh != nullptr) {
if (animationFPS > 0) this->isPlaying = true; if (animationFPS > 0) this->m_IsPlaying = true;
// Do NOT call playAnimation, otherwise infinite recursion occurs // Do NOT call playAnimation, otherwise infinite recursion occurs
// (it calls setAnimationFPS). // (it calls setAnimationFPS).
this->m_LoadedMesh->setAnimationSpeed(animationFPS); this->m_LoadedMesh->setAnimationSpeed(animationFPS);
@ -728,8 +758,8 @@ u32 Engine::animationFPS()
void Engine::run() void Engine::run()
{ {
u32 timePerFrame = 1000.0f; u32 timePerFrame = 1000.0f;
if (this->worldFPS > 0) { if (this->m_WorldFPS > 0) {
timePerFrame = static_cast<u32>(1000.0f / this->worldFPS); timePerFrame = static_cast<u32>(1000.0f / this->m_WorldFPS);
} }
ITimer* timer = m_Device->getTimer(); ITimer* timer = m_Device->getTimer();
@ -739,7 +769,7 @@ void Engine::run()
checkResize(); checkResize();
if (this->m_LoadedMesh != nullptr) { if (this->m_LoadedMesh != nullptr) {
if (isPlaying) { if (m_IsPlaying) {
this->m_LoadedMesh->setLoopMode(true); this->m_LoadedMesh->setLoopMode(true);
this->m_UserInterface->playbackSetFrameEditBox->setText( this->m_UserInterface->playbackSetFrameEditBox->setText(
Utility::toWstring(this->m_LoadedMesh->getFrameNr()).c_str() Utility::toWstring(this->m_LoadedMesh->getFrameNr()).c_str()

View File

@ -25,7 +25,6 @@ class Engine {
friend class View; friend class View;
private: private:
std::wstring m_NextPath;
irr::IrrlichtDevice* m_Device; irr::IrrlichtDevice* m_Device;
irr::video::IVideoDriver* m_Driver; irr::video::IVideoDriver* m_Driver;
irr::scene::ISceneManager* m_Scene; irr::scene::ISceneManager* m_Scene;
@ -51,10 +50,10 @@ private:
void checkResize(); void checkResize();
irr::gui::IGUIEnvironment* getGUIEnvironment() const; irr::gui::IGUIEnvironment* getGUIEnvironment() const;
irr::s32 getNumberOfVertices(); irr::s32 getNumberOfVertices();
bool isPlaying; bool m_IsPlaying;
irr::u32 worldFPS; irr::u32 m_WorldFPS;
irr::u32 prevFPS; irr::u32 m_PrevFPS;
std::vector<std::wstring> textureExtensions; std::vector<std::wstring> m_TextureExtensions;
// Making materials in contructor or setupScene causes segfault at // Making materials in contructor or setupScene causes segfault at
// `m_Driver->setMaterial(*lineX);` in // `m_Driver->setMaterial(*lineX);` in
// `Engine::drawAxisLines` for unknown reason: // `Engine::drawAxisLines` for unknown reason:
@ -69,9 +68,9 @@ private:
irr::s32 LMouseState, RMouseState; irr::s32 LMouseState, RMouseState;
public: public:
std::wstring m_PreviousPath; std::wstring m_LoadedMeshPath;
std::wstring m_PrevTexturePath; std::wstring m_LoadedTexturePath;
irr::f32 axisLength; irr::f32 m_AxisLength;
bool m_zUp; bool m_zUp;
Engine(); Engine();

View File

@ -9,6 +9,7 @@ bird: [github.com/poikilos/mobs_sky](https://github.com/poikilos/mobs_sky)
Website: [poikilos.org](https://poikilos.org) Website: [poikilos.org](https://poikilos.org)
## Main Features in poikilos fork ## Main Features in poikilos fork
* stabilized (makes sure font, model or texture loads before using; * stabilized (makes sure font, model or texture loads before using;
makes sure model is loaded before setting View options) makes sure model is loaded before setting View options)
@ -23,14 +24,46 @@ Website: [poikilos.org](https://poikilos.org)
* export feature: COLLADA (non-Blender), IRR (Irrlicht Scene settings * export feature: COLLADA (non-Blender), IRR (Irrlicht Scene settings
and mesh file paths only), IRRMESH (Static Irrlicht Mesh), OBJ and mesh file paths only), IRRMESH (Static Irrlicht Mesh), OBJ
(Wavefront), STL (stereolithography) (Wavefront), STL (stereolithography)
* Turn off interpolation if loadNextTexture (F3) detects minetest * Turn off interpolation if loadNextTexture (F3) detects the following
directory structure Minetest-like directory structure and texture naming:
(../textures/<texture filename based on model name>) "<texture directory>/<texture filename based on model name>" where
"<texture directory>" is either `.` (same directory as model)
or `../textures` (where model would be in a parallel directory next to
textures).
## Related Projects:
## Related Software
- [https://github.com/stujones11/SAM-Viewer](SAM-Viewer): View a - [https://github.com/stujones11/SAM-Viewer](SAM-Viewer): View a
minetest player model and see the effect of changing various wield minetest player model and see the effect of changing various wield
settings that are available in the minetest Lua API. settings that are available in the minetest Lua API.
- Blitz3d: Blitz3d was released
[on GitHub](https://github.com/blitz-research/blitz3d) under the
zlib/libpng license in 2014!
- Blitz3D plug-in for [Ultimate Unwrap
3D](https://www.unwrap3d.com/u3d/formats.aspx): Ultimate Unwrap 3D is
a standalone unwrapping tool ("UV Mapping Software").
- Milkshape can export B3D and import x without animations.
- TREEmagik Plus by AlienCodec (the original version is now
[free](http://www.aliencodec.com/Aliencodec%C2%A9+-+Software+Developers);
superceded by TREEmagik-G2) can export to b3d.
## B3D Format
B3D in this case is the Blitz3D model format.
- "stores model information in 'chunks;' may contain textures, brushes,
vertices, triangles, meshes, bones, or animation data."
-<https://fileinfo.com/extension/b3d>
### What it is not
The B3D format (Blitz3D format) supported by Irrlicht has nothing to do
with other formats which also have the B3D extension.
- It is not [.B3D - Maxon Bodypaint 3D texture
file](http://http.maxon.net/pub/bp2/docu/bodypaint3d_r2_reference_e.pdf),
an internal format that Cinema4D uses to store [multi-layer
textures](https://forums.creativecow.net/docs/forums/post.php?forumid=19&postid=236712&univpostid=236712&pview=t&archive=T).
- It is not ASCII
- not [.B3D - Ben's 3D Format](https://www.bcchang.com/research/vr/b3d.php)
## Compile ## Compile
(the original version of this section is from (the original version of this section is from
@ -91,6 +124,7 @@ only applies to Visual Studio users.)
gnu-free/FreeSansBold.ttf, dejavu/DejaVuSans-Bold.ttf, gnu-free/FreeSansBold.ttf, dejavu/DejaVuSans-Bold.ttf,
google-droid/DroidSans-Bold.ttf google-droid/DroidSans-Bold.ttf
## Install ## Install
### Windows ### Windows
* If you are not using a release version, compile the program (see * If you are not using a release version, compile the program (see
@ -135,14 +169,17 @@ only applies to Visual Studio users.)
animation runs as 30 fps (Irrlicht does interpolation automatically). animation runs as 30 fps (Irrlicht does interpolation automatically).
- Edit the frame rate manually using the input box under "Faster" and - Edit the frame rate manually using the input box under "Faster" and
"Slower." "Slower."
* `F3` / `Shift F3`: Cycle through textures in `../textures` using `F3` * `F3` / `Shift F3`: Cycle through textures where the filename contains
key (`Shift` to go backward) such as for Minetest mods, where model the model filename (or that without underscores) in the current
must be in `modname/models/` and texture must be in directory or `../textures`. If there are no matches, use a list of
`modname/textures/`. all found textures. The `F3` key goes to the next texture file (hold
- If `"../textures/" + basename(modelName) + ".png"` or `".jpg"` is `Shift` and press`F3` to go backward), but does nothing on the first
present, pressing `F3` for the first time will load it. press if the model had loaded its own texture.
- If `../textures` doesn't exist relative to the model file's - Example: Both automatic loading (when you open a mesh) and manually
directory, the model file's own directory will be used. cycling using F3 works for Minetest mods, where the model should be
in `modname/models/` and the texture should be in
`modname/textures/` (but occasionally is in the same directory as
the model).
* `Ctrl i`: toggle texture interpolation (shortcut for View, Texture * `Ctrl i`: toggle texture interpolation (shortcut for View, Texture
Interpolation) Interpolation)
* `F5`: Reload last model file * `F5`: Reload last model file
@ -154,6 +191,7 @@ only applies to Visual Studio users.)
* View, choose "Up" axis: change camera "up" axis to Z or Y (Y is * View, choose "Up" axis: change camera "up" axis to Z or Y (Y is
default; automatically changed to Z when 3ds file is loaded) default; automatically changed to Z when 3ds file is loaded)
## Known Issues ## Known Issues
* Warn on missing texture. * Warn on missing texture.
* Test and complete install.bat on Windows. * Test and complete install.bat on Windows.
@ -162,6 +200,7 @@ only applies to Visual Studio users.)
* (View.cpp) Set pitch correctly for shift & middle mouse button drag. * (View.cpp) Set pitch correctly for shift & middle mouse button drag.
* Lighting not correct until you rotate or enable z-Up * Lighting not correct until you rotate or enable z-Up
## Authors ## Authors
* ClearSansRegular.ttf (**Apache 2.0 License**) by Intel * ClearSansRegular.ttf (**Apache 2.0 License**) by Intel
<https://01.org/clear-sans> via <https://01.org/clear-sans> via
@ -181,3 +220,19 @@ only applies to Visual Studio users.)
**GPL v3** as per <https://code.google.com/archive/p/b3view/> **GPL v3** as per <https://code.google.com/archive/p/b3view/>
(see [LICENSE](https://github.com/poikilos/b3view/blob/master/LICENSE) (see [LICENSE](https://github.com/poikilos/b3view/blob/master/LICENSE)
file in your favorite text editor). file in your favorite text editor).
## Developer Notes
### Regression Tests
#### Manipulating mesh on failed load
- steps to reproduce
- File, Open, choose a mesh file such as animal_bat.b3d
- File, Open, choose a texture (purposely incorrect input)
- incorrect behaviors:
- manipulating the loaded scene, such as calling remove()
- SEGFAULT
- correct behaviors:
- Do nothing to the current scene.
- Show a message saying that the format is incorrect.

View File

@ -185,7 +185,7 @@ void UserInterface::setupUserInterface()
y += size_y + spacing_y; y += size_y + spacing_y;
axisSizeEditBox = m_Gui->addEditBox( axisSizeEditBox = m_Gui->addEditBox(
L"", std::to_wstring(this->m_Engine->m_AxisLength).c_str(),
rect<s32>(vector2d<s32>(spacing_x, y), rect<s32>(vector2d<s32>(spacing_x, y),
dimension2d<s32>(size_x, size_y)), dimension2d<s32>(size_x, size_y)),
true, true,
@ -255,7 +255,7 @@ void UserInterface::displayLoadTextureDialog()
void UserInterface::incrementFrame(f32 frameCount, bool enableRound) void UserInterface::incrementFrame(f32 frameCount, bool enableRound)
{ {
if (this->m_Engine->m_LoadedMesh != nullptr) { if (this->m_Engine->m_LoadedMesh != nullptr) {
if (this->m_Engine->isPlaying) if (this->m_Engine->m_IsPlaying)
this->m_Engine->toggleAnimation(); this->m_Engine->toggleAnimation();
this->m_Engine->m_LoadedMesh->setCurrentFrame( this->m_Engine->m_LoadedMesh->setCurrentFrame(
enableRound enableRound
@ -475,21 +475,36 @@ void UserInterface::drawStatusLine() const
{ {
} }
bool UserInterface::OnSelectMesh() {
this->m_MatchingTextures.clear();
this->m_AllTextures.clear();
return true;
}
/**
* Load the next texture from the list of found textures.
* Files are only listed once for speed, so you must reload the
* model to trigger another list ("dir") operation (since loading
* a mesh calls OnSelectMesh() which clears allTextures and matchingTextures).
*
* @param direction Specify <0 to choose previous texture, >0 for next, 0 to
* reload current texture if any; otherwise, only select a texture if any
* matching textures (named like model) are present in . or ../textures.
* @return Any texture was loaded (true/false).
*/
bool UserInterface::loadNextTexture(int direction) bool UserInterface::loadNextTexture(int direction)
{ {
cerr << "Loading texture..." << flush;
bool ret = false; bool ret = false;
this->m_Engine->m_NextPath = L"";
std::wstring basePath = L"."; std::wstring basePath = L".";
if (this->m_Engine->m_PreviousPath.length() > 0) { if (this->m_Engine->m_LoadedMeshPath.length() > 0) {
std::wstring prevModelName = Utility::basename( std::wstring prevModelName = Utility::basename(
this->m_Engine->m_PreviousPath this->m_Engine->m_LoadedMeshPath
); );
//vector<wstring> dotExtensions;
//dotExtensions.push_back(L".png");
//dotExtensions.push_back(L".jpg");
wstring foundPath; wstring foundPath;
wstring prevModelNoExt; wstring prevModelNoExt;
prevModelNoExt = Utility::withoutExtension(prevModelName); prevModelNoExt = Utility::withoutExtension(prevModelName);
/*
vector<wstring> names; vector<wstring> names;
names.push_back(prevModelNoExt+L"_mesh"); names.push_back(prevModelNoExt+L"_mesh");
names.push_back(prevModelNoExt); names.push_back(prevModelNoExt);
@ -506,185 +521,148 @@ bool UserInterface::loadNextTexture(int direction)
names.push_back(prevModelNoExt+L"_f"); names.push_back(prevModelNoExt+L"_f");
names.push_back(prevModelNoExt+L"_male"); names.push_back(prevModelNoExt+L"_male");
names.push_back(prevModelNoExt+L"_m"); names.push_back(prevModelNoExt+L"_m");
*/
vector<wstring> badSuffixes; vector<wstring> badSuffixes;
badSuffixes.push_back(L"_inv"); badSuffixes.push_back(L"_inv");
std::wstring lastDirPath = Utility::parentOfPath( std::wstring lastDirPath = Utility::parentOfPath(
this->m_Engine->m_PreviousPath this->m_Engine->m_LoadedMeshPath
); );
std::wstring parentPath = Utility::parentOfPath(lastDirPath); std::wstring parentPath = Utility::parentOfPath(lastDirPath);
std::wstring dirSeparator = Utility::delimiter( std::wstring dirSeparator = Utility::delimiter(
this->m_Engine->m_PreviousPath this->m_Engine->m_LoadedMeshPath
); );
std::wstring texturesPath = parentPath + dirSeparator + L"textures"; std::wstring texturesPath = parentPath + dirSeparator + L"textures";
std::wstring tryTexPath = texturesPath + dirSeparator + prevModelNoExt std::wstring tryTexPath = texturesPath + dirSeparator + prevModelNoExt
+ L".png"; + L".png";
if (direction == 0 && Utility::isFile(tryTexPath)) { vector<wstring> texturePaths;
this->m_Engine->m_NextPath = tryTexPath;
this->m_Engine->loadTexture(this->m_Engine->m_NextPath);
} else {
tryTexPath = lastDirPath + dirSeparator
+ prevModelNoExt + L".png";
if (direction == 0 && Utility::isFile(tryTexPath)) {
this->m_Engine->m_NextPath = tryTexPath;
ret = this->m_Engine->loadTexture(this->m_Engine->m_NextPath);
} else {
std::wstring path = texturesPath;
if (!fs::is_directory(fs::status(path))) texturePaths.push_back(lastDirPath);
path = lastDirPath; // cycle in model's directory instead
fs::directory_iterator end_itr; // default yields past-the-end if (fs::is_directory(fs::status(texturesPath))) {
texturePaths.push_back(texturesPath);
}
vector<wstring> dotExts;
for (auto ext : this->m_Engine->m_TextureExtensions) {
dotExts.push_back(L"." + ext);
}
std::wstring nextPath = L""; if (this->m_MatchingTextures.size() + this->m_AllTextures.size() < 1) {
std::wstring retroPath = L""; for (auto path : texturePaths) {
std::wstring lastPath = L""; for (const auto& itr : fs::directory_iterator(path)) {
if (fs::is_regular_file(itr.status())) {
bool found = false; std::wstring name = itr.path().filename().wstring();
bool force = false; std::wstring suffix = Utility::getSuffix(name, dotExts,
wstring tryPath; true);
if (fs::is_directory(fs::status(path))) { bool isUsable = true;
if (this->m_Engine->m_PrevTexturePath.length() == 0) { std::wstring nameNoExt = Utility::withoutExtension(
// if (this->m_Engine->m_PreviousPath.length() > 0) { name
for (auto name : names) {
for (auto extension : this->m_Engine->textureExtensions) {
tryPath = texturesPath + dirSeparator
+ name + L"." + extension;
// tryPath = Utility::toWstring(Utility::toString(tryPath));
if (Utility::isFile(tryPath)) {
foundPath = tryPath;
break;
}
// else
// debug() << " - no '"
// << Utility::toString(tryPath)
// << "'" << endl;
}
if (foundPath.length() > 0) {
break;
}
}
if (foundPath.length() > 0) {
nextPath = foundPath;
found = true;
force = true;
this->m_Engine->setEnableTextureInterpolation(false);
viewMenu->setItemChecked(
viewTextureInterpolationIdx,
this->m_Engine->getEnableTextureInterpolation()
);
} else {
nextPath = tryPath;
found = true;
force = true;
}
//}
}
// Do fuzzy texture name search
// (If found no texture yet, match instead of predict name):
if ((this->m_Engine->m_PrevTexturePath.length() == 0)
&& (foundPath.length() == 0)) {
for (const auto& itr : fs::directory_iterator(path)) {
std::wstring ext = Utility::extensionOf(
itr.path().wstring()
); // no dot!
std::wstring nameNoExt = Utility::withoutExtension(itr.path().filename().wstring());
// std::wstring rightName = Utility::rightOf(nameNoExt, L"_", true);
// std::wstring rightLastName = Utility::rightOfLast(nameNoExt, L"_", true);
// debug() << "itr.path().filename().wstring(): " << itr.path().filename().c_str() << endl;
if (Utility::startsWith(nameNoExt, prevModelNoExt)) {
wstring remainder = Utility::rightOf(nameNoExt, prevModelNoExt, true);
if (std::find(badSuffixes.begin(),
badSuffixes.end(), remainder)
== badSuffixes.end()) {
foundPath = itr.path().wstring();
nextPath = foundPath;
found = true;
force = true;
break;
}
}
for (auto name : names) {
for (auto extension : this->m_Engine->textureExtensions) {
wstring tryEnd = name + L"." + extension;
//if (Utility::endsWith(nameNoExt, name)) {
if (Utility::endsWith(itr.path().filename().wstring(), tryEnd)) {
foundPath = itr.path().wstring();
nextPath = foundPath;
found = true;
force = true;
break;
}
else {
debug() << "!endsWith("
<< Utility::toString(itr.path().filename().wstring())
<< "," << Utility::toString(tryEnd)
<< endl;
// debug() << "!endsWith("
// << Utility::toString(nameNoExt)
// << "," << Utility::toString(name)
// << endl;
}
}
if (foundPath.length() > 0) {
break;
}
}
if (foundPath.length() > 0) {
break;
}
}
}
if (force) {
this->m_Engine->setEnableTextureInterpolation(false);
viewMenu->setItemChecked(
viewTextureInterpolationIdx,
this->m_Engine->getEnableTextureInterpolation()
); );
} if (Utility::endsWithAny(nameNoExt, badSuffixes, true))
for (const auto& itr : fs::directory_iterator(path)) { isUsable = false;
std::wstring ext = Utility::extensionOf( if (isUsable && suffix.length() > 0) {
itr.path().wstring() this->m_AllTextures.push_back(
); // no dot! path + dirSeparator + name
if (!is_directory(itr.status()) );
&& std::find(m_Engine->textureExtensions.begin(), if (Utility::startsWith(name, prevModelNoExt)) {
m_Engine->textureExtensions.end(), ext) this->m_MatchingTextures.push_back(
!= m_Engine->textureExtensions.end()) { path + dirSeparator + name
// cycle through files (go to next after );
// m_PrevTexturePath if any previously loaded, }
// otherwise first) else if (name.find(prevModelNoExt) != std::wstring::npos) {
if (nextPath.length() == 0) this->m_MatchingTextures.push_back(
nextPath = itr.path().wstring(); path + dirSeparator + name
lastPath = itr.path().wstring(); );
if (found && direction > 0) { }
if (!force) else if (name.find(Utility::replaceAll(prevModelNoExt, L"_", L"")) != std::wstring::npos) {
nextPath = itr.path().wstring(); this->m_MatchingTextures.push_back(
break; path + dirSeparator + name
);
} }
if (itr.path().wstring()
== this->m_Engine->m_PrevTexturePath)
found = true;
if (!found)
retroPath = itr.path().wstring();
} }
else debug() << Utility::toString(ext)
<< "is not a valid extension for: "
<< Utility::toString(itr.path().filename().wstring())
<< endl;
}
if (retroPath.length() == 0)
retroPath = lastPath; // previous is last if at start
if (direction < 0)
nextPath = retroPath;
if (nextPath.length() > 0) {
ret = this->m_Engine->loadTexture(nextPath);
} }
} }
} }
} }
vector<wstring> paths = this->m_MatchingTextures;
if (this->m_MatchingTextures.size() < 1) {
paths = this->m_AllTextures;
debug() << "There were no matching textures."
<< " The entire list of " << this->m_AllTextures.size()
<< " found textures will be used." << std::endl;
}
else {
// Assume the user wants to view name-matched texture using
// the render settings of Minetest.
this->m_Engine->setEnableTextureInterpolation(false);
viewMenu->setItemChecked(
viewTextureInterpolationIdx,
this->m_Engine->getEnableTextureInterpolation()
);
}
std::wstring prevTexture = L"";
std::wstring nextTexture = L"";
std::wstring lastTexture = L"";
std::wstring firstTexture = L"";
bool found = false;
for (auto path : paths) {
if (firstTexture.length() == 0)
firstTexture = path;
lastTexture = path;
if (this->m_Engine->m_LoadedTexturePath.length() > 0) {
if (path == this->m_Engine->m_LoadedTexturePath) {
found = true;
}
else if (!found) {
prevTexture = path;
}
else {
if (nextTexture.length() == 0)
nextTexture = path;
}
}
else {
prevTexture = path; // Use the last one as the previous.
if (nextTexture.length() == 0)
nextTexture = path;
}
}
if (nextTexture.length() == 0)
nextTexture = firstTexture; // The last is current, so next is 1st.
if (prevTexture.length() == 0) {
if (lastTexture != firstTexture)
prevTexture = lastTexture; // Wrap to end.
else
prevTexture = firstTexture; // Use the only texture.
}
if (lastTexture.length() > 0) {
if (direction < 0) {
ret = this->m_Engine->loadTexture(prevTexture);
}
else if (direction > 0) {
ret = this->m_Engine->loadTexture(nextTexture);
}
else {
// If direction is 0 (such as when a model is loaded that has
// no texture), only load a preloaded or matching texture.
if (this->m_Engine->m_LoadedTexturePath.length() > 0) {
ret = this->m_Engine->loadTexture(
this->m_Engine->m_LoadedTexturePath
);
}
else if (this->m_MatchingTextures.size() >= 1) {
ret = this->m_Engine->loadTexture(firstTexture);
}
}
}
else if (this->m_Engine->m_LoadedTexturePath.length() > 0) {
ret = this->m_Engine->loadTexture(
this->m_Engine->m_LoadedTexturePath
);
}
} else } else
debug() << "Can't cycle texture since no file was opened" << endl; debug() << "Can't cycle texture since no file was opened" << endl;
cerr << (ret?"OK":"FAILED") << endl;
return ret; return ret;
} }
@ -705,8 +683,8 @@ void UserInterface::exportMeshToHome(std::string extension)
std::cout << "Your PATH is: " << where.c_str() << '\n'; std::cout << "Your PATH is: " << where.c_str() << '\n';
} }
std::string name = ""; std::string name = "";
if (m_Engine->m_PreviousPath.length() > 0) { if (m_Engine->m_LoadedMeshPath.length() > 0) {
name = Utility::toString(Utility::withoutExtension(Utility::basename(m_Engine->m_PreviousPath))); name = Utility::toString(Utility::withoutExtension(Utility::basename(m_Engine->m_LoadedMeshPath)));
} }
wstring result = m_Engine->saveMesh(where, name, extension); wstring result = m_Engine->saveMesh(where, name, extension);
@ -837,7 +815,7 @@ bool UserInterface::OnEvent(const SEvent& event)
break; break;
case UIE_AXISSIZEEDITBOX: case UIE_AXISSIZEEDITBOX:
if (ge->EventType == EGET_EDITBOX_ENTER) { if (ge->EventType == EGET_EDITBOX_ENTER) {
this->m_Engine->axisLength = Utility::toF32( this->m_Engine->m_AxisLength = Utility::toF32(
this->axisSizeEditBox->getText() this->axisSizeEditBox->getText()
); );
} }
@ -858,7 +836,7 @@ bool UserInterface::OnEvent(const SEvent& event)
m_Engine->reloadTexture(); m_Engine->reloadTexture();
} }
else { else {
if (m_Engine->m_PreviousPath.length() > 0) { if (m_Engine->m_LoadedMeshPath.length() > 0) {
bool result = m_Engine->reloadMesh(); bool result = m_Engine->reloadMesh();
if (!result) { if (!result) {
this->m_Engine->m_Device->getGUIEnvironment()->addMessageBox( this->m_Engine->m_Device->getGUIEnvironment()->addMessageBox(

View File

@ -5,6 +5,7 @@
#include <irrlicht/irrlicht.h> #include <irrlicht/irrlicht.h>
#include <string> #include <string>
#include <vector>
// Forward declaration of class Engine // Forward declaration of class Engine
class Engine; class Engine;
@ -74,6 +75,8 @@ private:
irr::gui::IGUIWindow* playbackWindow; irr::gui::IGUIWindow* playbackWindow;
irr::core::dimension2d<irr::u32> m_WindowSize; // previous size irr::core::dimension2d<irr::u32> m_WindowSize; // previous size
std::vector<std::wstring> m_AllTextures;
std::vector<std::wstring> m_MatchingTextures;
public: public:
irr::gui::IGUIContextMenu* menu; irr::gui::IGUIContextMenu* menu;
irr::gui::IGUIContextMenu* fileMenu; irr::gui::IGUIContextMenu* fileMenu;
@ -104,6 +107,7 @@ public:
void drawStatusLine() const; void drawStatusLine() const;
bool loadNextTexture(int direction); bool loadNextTexture(int direction);
void exportMeshToHome(std::string extension); void exportMeshToHome(std::string extension);
bool OnSelectMesh();
// IEventReceiver // IEventReceiver
virtual bool OnEvent(const irr::SEvent& event); virtual bool OnEvent(const irr::SEvent& event);

View File

@ -8,6 +8,7 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include <assert.h>
#include "Debug.h" #include "Debug.h"
@ -21,6 +22,21 @@ void Utility::dumpVectorToConsole(const vector3df& vector)
debug() << "X: " << vector.X << " Y: " << vector.Y << " Z: " << vector.Z << endl; debug() << "X: " << vector.X << " Y: " << vector.Y << " Z: " << vector.Z << endl;
} }
int Utility::getTextureCount(const SMaterial& material) {
int count = 0;
for (irr::u32 ti = 0; ti < MATERIAL_MAX_TEXTURES; ti++)
if (material.getTexture(ti) != nullptr)
count++;
return count;
}
int Utility::getTextureCount(IAnimatedMeshSceneNode* node) {
int count = 0;
for (irr::u32 matIndex = 0; matIndex < node->getMaterialCount(); matIndex++) {
count += getTextureCount(node->getMaterial(matIndex));
}
return count;
}
void Utility::dumpMeshInfoToConsole(IAnimatedMeshSceneNode* node) void Utility::dumpMeshInfoToConsole(IAnimatedMeshSceneNode* node)
{ {
if (node == nullptr) { if (node == nullptr) {
@ -55,11 +71,7 @@ void Utility::dumpMeshInfoToConsole(IAnimatedMeshSceneNode* node)
<< material.Shininess << endl; << material.Shininess << endl;
// check for # textures // check for # textures
int textures = 0; debug() << "[MESH]: # of textures : " << Utility::getTextureCount(material) << endl;
for (irr::u32 ti = 0; ti < MATERIAL_MAX_TEXTURES; ti++)
if (material.getTexture(ti) != nullptr)
textures++;
debug() << "[MESH]: # of textures : " << textures << endl;
} }
} }
@ -140,6 +152,44 @@ bool Utility::startsWith(const std::wstring& haystack, const std::wstring& needl
return found; return found;
} }
wstring Utility::replaceAll(const wstring &subject, const wstring &from, const wstring &to)
{
size_t i = 0;
if (from.length() == 0) {
return subject;
}
wstring result = subject;
while (i < result.length()) {
if (result.substr(i, from.length()) == from) {
result = result.substr(0, i) + to + result.substr(i + from.length());
i += to.length();
}
else {
i++;
}
}
return result;
}
std::string Utility::replaceAll(const std::string &subject, const std::string &from, const std::string &to)
{
size_t i = 0;
if (from.length() == 0) {
return subject;
}
std::string result = subject;
while (i < result.length()) {
if (result.substr(i, from.length()) == from) {
result = result.substr(0, i) + to + result.substr(i + from.length());
i += to.length();
}
else {
i++;
}
}
return result;
}
bool Utility::endsWith(const std::wstring& haystack, const std::wstring& needle) { bool Utility::endsWith(const std::wstring& haystack, const std::wstring& needle) {
bool found = false; bool found = false;
if (haystack.length() >= needle.length()) { if (haystack.length() >= needle.length()) {
@ -150,6 +200,48 @@ bool Utility::endsWith(const std::wstring& haystack, const std::wstring& needle)
return found; return found;
} }
bool Utility::startsWithAny(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI) {
return getPrefix(haystack, needles, CI).length() > 0;
}
bool Utility::endsWithAny(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI) {
return getSuffix(haystack, needles, CI).length() > 0;
}
std::wstring Utility::getPrefix(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI) {
if (CI) {
std::wstring haystackLower = Utility::toLower(haystack);
for (auto needle : needles) {
if (Utility::startsWith(haystackLower, Utility::toLower(needle)))
return needle;
}
}
else {
for (auto needle : needles) {
if (Utility::startsWith(haystack, needle))
return needle;
}
}
return L"";
}
std::wstring Utility::getSuffix(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI) {
if (CI) {
std::wstring haystackLower = Utility::toLower(haystack);
for (auto needle : needles) {
if (Utility::endsWith(haystackLower, Utility::toLower(needle)))
return needle;
}
}
else {
for (auto needle : needles) {
if (Utility::endsWith(haystack, needle))
return needle;
}
}
return L"";
}
/// Get any substring to the left of the last delimiter. /// Get any substring to the left of the last delimiter.
/// allIfNotFound: Return whole string on no delimiter, vs empty string. /// allIfNotFound: Return whole string on no delimiter, vs empty string.
wstring Utility::leftOfLast(const wstring& path, const wstring& delimiter, bool allIfNotFound) wstring Utility::leftOfLast(const wstring& path, const wstring& delimiter, bool allIfNotFound)
@ -362,3 +454,40 @@ std::string Utility::toString(irr::f32 val)
// return abs(f2-f1) < .00000001; // TODO: kEpsilon? (see also // return abs(f2-f1) < .00000001; // TODO: kEpsilon? (see also
// // <https://en.wikipedia.org/wiki/Machine_epsilon#How_to_determine_machine_epsilon>) // // <https://en.wikipedia.org/wiki/Machine_epsilon#How_to_determine_machine_epsilon>)
// } // }
TestUtility::TestUtility() {
std::cerr << "TestUtility..." << std::flush;
testReplaceAll(L"***water_dragon***", L"_", L"", L"***waterdragon***");
testReplaceAll(L"*water_dragon*", L"*", L"***", L"***water_dragon***");
testReplaceAll(L"***water_dragon***", L"***", L"", L"water_dragon");
testReplaceAll(L"***water_dragon***", L"", L"***", L"***water_dragon***"); // do nothing
std::cerr << "OK" << std::endl;
}
void TestUtility::testReplaceAll(const wstring &subject, const wstring &from, const wstring &to, const wstring &expectedResult)
{
this->assertEqual(Utility::replaceAll(subject, from, to), expectedResult);
};
void TestUtility::testReplaceAll(const std::string &subject, const std::string &from, const std::string &to, const std::string &expectedResult)
{
std::string result = Utility::replaceAll(subject, from, to);
this->assertEqual(result, expectedResult);
};
void TestUtility::assertEqual(const wstring& subject, const wstring& expectedResult)
{
if (subject != expectedResult) {
cerr << "The test expected \"" << Utility::toString(expectedResult) << "\" but got \"" << Utility::toString(subject) << std::endl;
}
assert(subject == expectedResult);
}
void TestUtility::assertEqual(const std::string subject, const std::string expectedResult)
{
if (subject != expectedResult) {
cerr << "The test expected \"" << expectedResult << "\" but got \"" << subject << std::endl;
}
assert(subject == expectedResult);
}
static TestUtility testutility;

View File

@ -5,10 +5,13 @@
#include <ctime> #include <ctime>
#include <string> #include <string>
#include <vector>
class Utility { class Utility {
public: public:
static void dumpVectorToConsole(const irr::core::vector3df& vector); static void dumpVectorToConsole(const irr::core::vector3df& vector);
static int getTextureCount(const irr::video::SMaterial& material);
static int getTextureCount(irr::scene::IAnimatedMeshSceneNode* node);
static void dumpMeshInfoToConsole(irr::scene::IAnimatedMeshSceneNode* node); static void dumpMeshInfoToConsole(irr::scene::IAnimatedMeshSceneNode* node);
static std::wstring parentOfPath(const std::wstring& path); static std::wstring parentOfPath(const std::wstring& path);
static std::wstring basename(const std::wstring& path); static std::wstring basename(const std::wstring& path);
@ -17,7 +20,13 @@ public:
static std::wstring rightOf(const std::wstring& path, const std::wstring& delimiter, bool allIfNotFound); static std::wstring rightOf(const std::wstring& path, const std::wstring& delimiter, bool allIfNotFound);
static std::wstring rightOfLast(const std::wstring& path, const std::wstring& delimiter, bool allIfNotFound); static std::wstring rightOfLast(const std::wstring& path, const std::wstring& delimiter, bool allIfNotFound);
static bool startsWith(const std::wstring& haystack, const std::wstring& needle); static bool startsWith(const std::wstring& haystack, const std::wstring& needle);
static std::wstring replaceAll(const std::wstring& subject, const std::wstring& from, const std::wstring& to);
static std::string replaceAll(const std::string& subject, const std::string& from, const std::string& to);
static bool endsWith(const std::wstring& haystack, const std::wstring& needle); static bool endsWith(const std::wstring& haystack, const std::wstring& needle);
static std::wstring getPrefix(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI);
static std::wstring getSuffix(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI);
static bool startsWithAny(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI);
static bool endsWithAny(const std::wstring& haystack, const std::vector<std::wstring>& needles, bool CI);
static std::wstring withoutExtension(const std::wstring& path); static std::wstring withoutExtension(const std::wstring& path);
static std::wstring extensionOf(const std::wstring& path); static std::wstring extensionOf(const std::wstring& path);
static std::wstring delimiter(const std::wstring& path); static std::wstring delimiter(const std::wstring& path);
@ -44,4 +53,13 @@ public:
} }
}; };
class TestUtility {
public:
TestUtility();
void assertEqual(const std::wstring& subject, const std::wstring& expectedResult);
void assertEqual(const std::string subject, const std::string expectedResult);
void testReplaceAll(const std::wstring& subject, const std::wstring& from, const std::wstring& to, const std::wstring& expectedResult);
void testReplaceAll(const std::string& subject, const std::string& from, const std::string& to, const std::string& expectedResult);
};
#endif // UTILS_H #endif // UTILS_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 58 KiB