Initial commit
This commit is contained in:
parent
c8aa77b549
commit
d13d83b1ff
65
.gitignore
vendored
65
.gitignore
vendored
@ -1,34 +1,33 @@
|
|||||||
# ---> C++
|
*.un~
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
*.gch
|
||||||
*.pch
|
*.txt
|
||||||
|
tags
|
||||||
# Compiled Dynamic libraries
|
*.pyc
|
||||||
*.so
|
*.attr
|
||||||
*.dylib
|
*.DS_Store
|
||||||
*.dll
|
.clang_complete
|
||||||
|
tags
|
||||||
# Fortran module files
|
ctags
|
||||||
*.mod
|
build/
|
||||||
*.smod
|
*/vc/
|
||||||
|
vc/
|
||||||
# Compiled Static libraries
|
*/xcode/
|
||||||
*.lai
|
xcode/
|
||||||
*.la
|
*.sublime-project
|
||||||
*.a
|
*.sublime-workspace
|
||||||
*.lib
|
uvChecker/.idea/
|
||||||
|
uvChecker/cmake-build-debug/
|
||||||
# Executables
|
modules/
|
||||||
*.exe
|
plug-ins/
|
||||||
*.out
|
.idea/
|
||||||
*.app
|
.idea
|
||||||
|
cmake-build-debug/
|
||||||
|
cmake-build-release/
|
||||||
|
cmake-build-maya/
|
||||||
|
cmake-build-maya_debug
|
||||||
|
cmake-build-maya_release
|
||||||
|
!CMakeLists.txt
|
||||||
|
.clang
|
||||||
|
sublimeBuild.bat
|
||||||
|
*ccls*
|
||||||
|
settings.json
|
||||||
|
113
CMakeLists.txt
Normal file
113
CMakeLists.txt
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
project(CheckTools CXX)
|
||||||
|
|
||||||
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
|
if(NOT MAYA_ROOT_DIR)
|
||||||
|
message(FATAL_ERROR "MAYA_ROOT_DIR must be set!")
|
||||||
|
else()
|
||||||
|
message(STATUS "Using MAYA_ROOT_DIR ${MAYA_ROOT_DIR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(Maya INTERFACE)
|
||||||
|
target_compile_features(Maya INTERFACE cxx_std_11)
|
||||||
|
set_target_properties(Maya PROPERTIES
|
||||||
|
INTERFACE_POSITION_INDEPENDENT_CODE ON
|
||||||
|
)
|
||||||
|
target_compile_definitions(Maya INTERFACE
|
||||||
|
REQUIRE_IOSTREAM
|
||||||
|
_BOOL
|
||||||
|
)
|
||||||
|
target_compile_options(Maya INTERFACE
|
||||||
|
$<$<CXX_COMPILER_ID:GNU>:pthread -Wall -Wextra -Wconversion -Wsign-conversion -pedantic>
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:/W4>
|
||||||
|
)
|
||||||
|
# OS specific
|
||||||
|
if (WIN32)
|
||||||
|
target_compile_definitions(Maya INTERFACE NT_PLUGIN)
|
||||||
|
set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include)
|
||||||
|
set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/lib)
|
||||||
|
set(MAYA_PLUGIN_SUFFIX ".mll")
|
||||||
|
elseif (APPLE)
|
||||||
|
target_compile_definitions(Maya INTERFACE OSMac_)
|
||||||
|
set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include)
|
||||||
|
set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/Maya.app/Contents/MacOS)
|
||||||
|
set(MAYA_PLUGIN_SUFFIX ".bundle")
|
||||||
|
elseif (UNIX)
|
||||||
|
target_compile_definitions(Maya INTERFACE LINUX)
|
||||||
|
set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include)
|
||||||
|
set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/lib)
|
||||||
|
set(MAYA_PLUGIN_SUFFIX ".so")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Operating system not supported")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# find MPxCommand header
|
||||||
|
find_path(MPX_COMMAND_HEADER MPxCommand.h
|
||||||
|
PATH ${MAYA_INCLUDE_DIR}/maya
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
)
|
||||||
|
if(MPX_COMMAND_HEADER)
|
||||||
|
message(STATUS "Header: MPxCommand.h - FOUND")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Header: MPxCommand.h - NOT FOUND")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(Maya SYSTEM INTERFACE ${MAYA_INCLUDE_DIR})
|
||||||
|
|
||||||
|
# find libs
|
||||||
|
set(MAYA_LIBS
|
||||||
|
Foundation
|
||||||
|
OpenMaya
|
||||||
|
OpenMayaAnim
|
||||||
|
OpenMayaFX
|
||||||
|
OpenMayaRender
|
||||||
|
OpenMayaUI
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach(MAYA_LIB ${MAYA_LIBS})
|
||||||
|
find_library(LIB-${MAYA_LIB} ${MAYA_LIB}
|
||||||
|
PATH ${MAYA_LIB_DIR}
|
||||||
|
NO_DEFAULT_PATH)
|
||||||
|
if(LIB-${MAYA_LIB})
|
||||||
|
message(STATUS "Library: ${MAYA_LIB} - FOUND")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Library: ${MAYA_LIB} - NOT FOUND")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(Maya INTERFACE ${LIB-${MAYA_LIB}})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# add_maya_library
|
||||||
|
function(add_maya_library)
|
||||||
|
set(opt)
|
||||||
|
set(one NAME)
|
||||||
|
set(mul PRIVATE_SOURCE PUBLIC_SOURCE)
|
||||||
|
cmake_parse_arguments(ADD_MAYA_LIB "${opt}" "${one}" "${mul}" ${ARGN})
|
||||||
|
|
||||||
|
add_library(${ADD_MAYA_LIB_NAME} SHARED "")
|
||||||
|
target_link_libraries(${ADD_MAYA_LIB_NAME} PRIVATE Maya)
|
||||||
|
set_target_properties(${ADD_MAYA_LIB_NAME} PROPERTIES
|
||||||
|
PREFIX ""
|
||||||
|
SUFFIX ${MAYA_PLUGIN_SUFFIX}
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>: LINK_FLAGS /export:initializePlugin /export:uninitializePlugin>
|
||||||
|
)
|
||||||
|
target_sources(${ADD_MAYA_LIB_NAME}
|
||||||
|
PUBLIC
|
||||||
|
${ADD_MAYA_LIB_PUBLIC_SOURCE}
|
||||||
|
PRIVATE
|
||||||
|
${ADD_MAYA_LIB_PRIVATE_SOURCE}
|
||||||
|
)
|
||||||
|
endfunction(add_maya_library)
|
||||||
|
|
||||||
|
add_subdirectory(meshChecker)
|
||||||
|
add_subdirectory(uvChecker)
|
||||||
|
add_subdirectory(uvOverlapChecker)
|
||||||
|
|
||||||
|
|
||||||
|
if(MAYA_VERSION)
|
||||||
|
message(STATUS "MAYA_VERSION : ${MAYA_VERSION}")
|
||||||
|
else()
|
||||||
|
message(STATUS "MAYA_VERSION not set for install directory")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR})
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Michi Inoue
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
42
README.md
42
README.md
@ -1,2 +1,44 @@
|
|||||||
# CheckTools
|
# CheckTools
|
||||||
|
Mesh/UV check commands for maya
|
||||||
|
|
||||||
|
## [meshChecker](https://github.com/minoue/CheckTools/blob/master/meshChecker/)
|
||||||
|
Mesh/Topology checker
|
||||||
|
|
||||||
|
## [uvChecker](https://github.com/minoue/CheckTools/blob/master/uvChecker/)
|
||||||
|
General UV checker
|
||||||
|
|
||||||
|
## [findUvOverlaps](https://github.com/minoue/CheckTools/blob/master/uvOverlapChecker/)
|
||||||
|
UV overlap checker based on the Bentley–Ottmann algorithm
|
||||||
|
|
||||||
|
⚠️ **Warning** ⚠️
|
||||||
|
* Be sure to check if a mesh has no **unassigned UVs**, otherwise maya clashes.
|
||||||
|
|
||||||
|
## GUI
|
||||||
|
|
||||||
|
[ModelCheckerForMaya](https://github.com/minoue/ModelCheckerForMaya)
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
### Build requirements
|
||||||
|
C++11
|
||||||
|
|
||||||
|
### Linux/MacOS
|
||||||
|
```
|
||||||
|
>git clone https://github.com/minoue/CheckTools
|
||||||
|
>cd CheckTools
|
||||||
|
>mkdir build
|
||||||
|
>cd build
|
||||||
|
>cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DMAYA_VERSION=2020 -DMAYA_ROOT_DIR=path_to_maya_directory ../
|
||||||
|
>cmake --build . --config Release --target install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
eg. VS2019 and Maya2020
|
||||||
|
```
|
||||||
|
>git clone https://github.com/minoue/CheckTools
|
||||||
|
>cd CheckTools
|
||||||
|
>mkdir build
|
||||||
|
>cd build
|
||||||
|
>cmake -G "Visual Studio 16 2019" -DMAYA_VERSION=2020 -DMAYA_ROOT_DIR="C:\Program Files\Autodesk\Maya2020" ../
|
||||||
|
>cmake --build . --config Release --target install
|
||||||
|
```
|
||||||
|
BIN
images/UVsInNegative.png
Normal file
BIN
images/UVsInNegative.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
images/concaveUVs.png
Normal file
BIN
images/concaveUVs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
images/reversedUVs.png
Normal file
BIN
images/reversedUVs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
BIN
images/udimIntersections.png
Normal file
BIN
images/udimIntersections.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
images/unMappedFaces.png
Normal file
BIN
images/unMappedFaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
images/zeroAreaUVFaces.png
Normal file
BIN
images/zeroAreaUVFaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
98
include/ThreadPool.hpp
Normal file
98
include/ThreadPool.hpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#ifndef THREAD_POOL_H
|
||||||
|
#define THREAD_POOL_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <future>
|
||||||
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
class ThreadPool {
|
||||||
|
public:
|
||||||
|
ThreadPool(size_t);
|
||||||
|
template<class F, class... Args>
|
||||||
|
auto enqueue(F&& f, Args&&... args)
|
||||||
|
-> std::future<typename std::result_of<F(Args...)>::type>;
|
||||||
|
~ThreadPool();
|
||||||
|
private:
|
||||||
|
// need to keep track of threads so we can join them
|
||||||
|
std::vector< std::thread > workers;
|
||||||
|
// the task queue
|
||||||
|
std::queue< std::function<void()> > tasks;
|
||||||
|
|
||||||
|
// synchronization
|
||||||
|
std::mutex queue_mutex;
|
||||||
|
std::condition_variable condition;
|
||||||
|
bool stop;
|
||||||
|
};
|
||||||
|
|
||||||
|
// the constructor just launches some amount of workers
|
||||||
|
inline ThreadPool::ThreadPool(size_t threads)
|
||||||
|
: stop(false)
|
||||||
|
{
|
||||||
|
for(size_t i = 0;i<threads;++i)
|
||||||
|
workers.emplace_back(
|
||||||
|
[this]
|
||||||
|
{
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
std::function<void()> task;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(this->queue_mutex);
|
||||||
|
this->condition.wait(lock,
|
||||||
|
[this]{ return this->stop || !this->tasks.empty(); });
|
||||||
|
if(this->stop && this->tasks.empty())
|
||||||
|
return;
|
||||||
|
task = std::move(this->tasks.front());
|
||||||
|
this->tasks.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new work item to the pool
|
||||||
|
template<class F, class... Args>
|
||||||
|
auto ThreadPool::enqueue(F&& f, Args&&... args)
|
||||||
|
-> std::future<typename std::result_of<F(Args...)>::type>
|
||||||
|
{
|
||||||
|
using return_type = typename std::result_of<F(Args...)>::type;
|
||||||
|
|
||||||
|
auto task = std::make_shared< std::packaged_task<return_type()> >(
|
||||||
|
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::future<return_type> res = task->get_future();
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||||
|
|
||||||
|
// don't allow enqueueing after stopping the pool
|
||||||
|
if(stop)
|
||||||
|
throw std::runtime_error("enqueue on stopped ThreadPool");
|
||||||
|
|
||||||
|
tasks.emplace([task](){ (*task)(); });
|
||||||
|
}
|
||||||
|
condition.notify_one();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the destructor joins all threads
|
||||||
|
inline ThreadPool::~ThreadPool()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
condition.notify_all();
|
||||||
|
for(std::thread &worker: workers)
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
61
include/utils.hpp
Normal file
61
include/utils.hpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <maya/MDagPath.h>
|
||||||
|
#include <maya/MString.h>
|
||||||
|
#include <maya/MItDag.h>
|
||||||
|
|
||||||
|
|
||||||
|
inline float getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy)
|
||||||
|
{
|
||||||
|
return ((Ax * (By - Cy)) + (Bx * (Cy - Ay)) + (Cx * (Ay - By))) * 0.5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ResultType {
|
||||||
|
Face,
|
||||||
|
Vertex,
|
||||||
|
Edge,
|
||||||
|
UV
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void createResultString(const MDagPath& dagPath, ResultType type, int index, std::string& outPath)
|
||||||
|
{
|
||||||
|
outPath = std::string(dagPath.fullPathName().asChar());
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ResultType::Face: {
|
||||||
|
outPath += ".f[" + std::to_string(index) + "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResultType::Vertex: {
|
||||||
|
outPath += ".vtx[" + std::to_string(index) + "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResultType::Edge: {
|
||||||
|
outPath += ".e[" + std::to_string(index) + "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResultType::UV: {
|
||||||
|
outPath += ".map[" + std::to_string(index) + "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildHierarchy(const MDagPath& path, std::vector<std::string>& result)
|
||||||
|
{
|
||||||
|
|
||||||
|
MString name;
|
||||||
|
|
||||||
|
MItDag dagIter;
|
||||||
|
for (dagIter.reset(path, MItDag::kDepthFirst); !dagIter.isDone(); dagIter.next()) {
|
||||||
|
MObject obj = dagIter.currentItem();
|
||||||
|
|
||||||
|
if (obj.apiType() == MFn::kMesh) {
|
||||||
|
name = dagIter.fullPathName();
|
||||||
|
result.push_back(name.asChar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
meshChecker/CMakeLists.txt
Normal file
15
meshChecker/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
project(meshChecker CXX)
|
||||||
|
|
||||||
|
add_maya_library(NAME ${PROJECT_NAME}
|
||||||
|
PRIVATE_SOURCE
|
||||||
|
src/meshChecker.cpp
|
||||||
|
src/meshChecker.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(MAYA_TARGET_TYPE RUNTIME)
|
||||||
|
else ()
|
||||||
|
set(MAYA_TARGET_TYPE LIBRARY)
|
||||||
|
endif()
|
||||||
|
set(INSTALL_DIR ../plug-ins/${MAYA_VERSION})
|
||||||
|
install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR})
|
35
meshChecker/README.md
Normal file
35
meshChecker/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# MeshChecker
|
||||||
|
Mesh/Topology checker for my own
|
||||||
|
|
||||||
|
## Check numbers
|
||||||
|
0. Triangles
|
||||||
|
1. Ngons
|
||||||
|
2. Non-manifold edges
|
||||||
|
3. Lamina faces
|
||||||
|
4. Bi-valent faces
|
||||||
|
5. Zero area faces
|
||||||
|
6. Mesh border edges
|
||||||
|
7. Crease edges
|
||||||
|
8. Zero length edges
|
||||||
|
9. Vertex pnts attributes
|
||||||
|
10. Empty geometry (geo with 0 vertices)
|
||||||
|
11. Instance shpaes
|
||||||
|
12. Channel connections
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
| Longname | Shortname | Argument types | Default | Properties |
|
||||||
|
|:---------|----------:|:--------------:|:-------:|:----------:|
|
||||||
|
|check|c|int||C|
|
||||||
|
|maxFaceaArea|mfa|float|0.00001|C|
|
||||||
|
|minEdgeLength|mel|float|0.000001|C|
|
||||||
|
|doFix|fix|bool|false|c|
|
||||||
|
|
||||||
|
* 'fix' flag can be used for 'vertex pnts attribute' check
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```python
|
||||||
|
from maya import cmds
|
||||||
|
e = cmds.checkMesh("|pSphere1", c=0)
|
||||||
|
print e
|
||||||
|
[u'|pSphere1.f[360]', u'|pSphere1.f[361]', u'|pSphere1.f[362]', u'|pSphere1.f[363]', u'|pSphere1.f[364]', u'|pSphere1.f[365]', u'|pSphere1.f[366]', u'|pSphere1.f[367]', u'|pSphere1.f[368]', u'|pSphere1.f[369]', u'|pSphere1.f[370]', u'|pSphere1.f[371]', u'|pSphere1.f[372]', u'|pSphere1.f[373]', u'|pSphere1.f[374]', u'|pSphere1.f[375]', u'|pSphere1.f[376]', u'|pSphere1.f[377]', u'|pSphere1.f[378]', u'|pSphere1.f[379]', u'|pSphere1.f[380]', u'|pSphere1.f[381]', u'|pSphere1.f[382]', u'|pSphere1.f[383]', u'|pSphere1.f[384]', u'|pSphere1.f[385]', u'|pSphere1.f[386]', u'|pSphere1.f[387]', u'|pSphere1.f[388]', u'|pSphere1.f[389]', u'|pSphere1.f[390]', u'|pSphere1.f[391]', u'|pSphere1.f[392]', u'|pSphere1.f[393]', u'|pSphere1.f[394]', u'|pSphere1.f[395]', u'|pSphere1.f[396]', u'|pSphere1.f[397]', u'|pSphere1.f[398]', u'|pSphere1.f[399]']
|
||||||
|
```
|
723
meshChecker/src/meshChecker.cpp
Normal file
723
meshChecker/src/meshChecker.cpp
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
#include "meshChecker.hpp"
|
||||||
|
#include "../../include/ThreadPool.hpp"
|
||||||
|
#include "../../include/utils.hpp"
|
||||||
|
#include "maya/MApiNamespace.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <maya/MObject.h>
|
||||||
|
#include <maya/MArgDatabase.h>
|
||||||
|
#include <maya/MArgList.h>
|
||||||
|
#include <maya/MArrayDataHandle.h>
|
||||||
|
#include <maya/MDagPath.h>
|
||||||
|
#include <maya/MDataHandle.h>
|
||||||
|
#include <maya/MDoubleArray.h>
|
||||||
|
#include <maya/MFnDagNode.h>
|
||||||
|
#include <maya/MFnMesh.h>
|
||||||
|
#include <maya/MFnPlugin.h>
|
||||||
|
#include <maya/MGlobal.h>
|
||||||
|
#include <maya/MItDag.h>
|
||||||
|
#include <maya/MItMeshEdge.h>
|
||||||
|
#include <maya/MItMeshPolygon.h>
|
||||||
|
#include <maya/MItMeshVertex.h>
|
||||||
|
#include <maya/MPlug.h>
|
||||||
|
#include <maya/MSelectionList.h>
|
||||||
|
#include <maya/MString.h>
|
||||||
|
#include <maya/MSyntax.h>
|
||||||
|
#include <maya/MUintArray.h>
|
||||||
|
#include <maya/MPlugArray.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
static const char* const pluginCommandName = "checkMesh";
|
||||||
|
static const char* const pluginVersion = "2.3.0";
|
||||||
|
static const char* const pluginAuthor = "Michi Inoue";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<std::string> findTriangles(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
MFnMesh mesh(dagPath);
|
||||||
|
int numPoly = mesh.numPolygons();
|
||||||
|
|
||||||
|
for (int j = 0; j < numPoly; j++) {
|
||||||
|
if (mesh.polygonVertexCount(j) == 3) {
|
||||||
|
createResultString(dagPath, ResultType::Face, j, errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findNgons(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
MFnMesh mesh(dagPath);
|
||||||
|
int numPoly = mesh.numPolygons();
|
||||||
|
|
||||||
|
for (int i = 0; i < numPoly; i++) {
|
||||||
|
if (mesh.polygonVertexCount(i) >= 5) {
|
||||||
|
createResultString(dagPath, ResultType::Face, i, errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findNonManifoldEdges(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
|
||||||
|
for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) {
|
||||||
|
int face_count;
|
||||||
|
edgeIter.numConnectedFaces(face_count);
|
||||||
|
if (face_count > 2) {
|
||||||
|
createResultString(dagPath, ResultType::Edge, edgeIter.index(), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findLaminaFaces(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
|
||||||
|
for (MItMeshPolygon polyIter(dagPath); !polyIter.isDone(); polyIter.next()) {
|
||||||
|
if (polyIter.isLamina()) {
|
||||||
|
createResultString(dagPath, ResultType::Face, static_cast<int>(polyIter.index()), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findBiValentFaces(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
MIntArray connectedFaces;
|
||||||
|
MIntArray connectedEdges;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
|
||||||
|
for (MItMeshVertex vtxIter(dagPath); !vtxIter.isDone(); vtxIter.next()) {
|
||||||
|
vtxIter.getConnectedFaces(connectedFaces);
|
||||||
|
vtxIter.getConnectedEdges(connectedEdges);
|
||||||
|
|
||||||
|
if (connectedFaces.length() == 2 && connectedEdges.length() == 2) {
|
||||||
|
createResultString(dagPath, ResultType::Vertex, vtxIter.index(), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findZeroAreaFaces(std::vector<std::string>* paths, double maxFaceArea)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
|
||||||
|
for (MItMeshPolygon polyIter(dagPath); !polyIter.isDone(); polyIter.next()) {
|
||||||
|
double area;
|
||||||
|
polyIter.getArea(area);
|
||||||
|
if (area < maxFaceArea) {
|
||||||
|
createResultString(dagPath, ResultType::Face, static_cast<int>(polyIter.index()), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findMeshBorderEdges(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
|
||||||
|
for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) {
|
||||||
|
if (edgeIter.onBoundary()) {
|
||||||
|
createResultString(dagPath, ResultType::Edge, edgeIter.index(), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findCreaseEdges(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
MFnMesh mesh(dagPath);
|
||||||
|
|
||||||
|
MUintArray edgeIds;
|
||||||
|
MDoubleArray creaseData;
|
||||||
|
mesh.getCreaseEdges(edgeIds, creaseData);
|
||||||
|
|
||||||
|
unsigned int edgeIdLength = edgeIds.length();
|
||||||
|
|
||||||
|
for (unsigned int j = 0; j < edgeIdLength; j++) {
|
||||||
|
createResultString(dagPath, ResultType::Edge, static_cast<int>(edgeIds[j]), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findZeroLengthEdges(std::vector<std::string>* paths, double minEdgeLength)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) {
|
||||||
|
double length;
|
||||||
|
edgeIter.getLength(length);
|
||||||
|
if (length < minEdgeLength) {
|
||||||
|
createResultString(dagPath, ResultType::Edge, static_cast<int>(edgeIter.index()), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> hasVertexPntsAttr(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
dagPath.extendToShape();
|
||||||
|
MFnDagNode dagNode(dagPath);
|
||||||
|
MFnMesh mesh(dagPath);
|
||||||
|
MPlug pntsArray = mesh.findPlug("pnts", false);
|
||||||
|
MDataHandle dataHandle = pntsArray.asMDataHandle();
|
||||||
|
MArrayDataHandle arrayDataHandle(dataHandle);
|
||||||
|
MDataHandle outputHandle;
|
||||||
|
|
||||||
|
unsigned int numElements = arrayDataHandle.elementCount();
|
||||||
|
|
||||||
|
if (numElements == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
outputHandle = arrayDataHandle.outputValue();
|
||||||
|
|
||||||
|
const float3& xyz = outputHandle.asFloat3();
|
||||||
|
|
||||||
|
if (xyz[0] != 0.0) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (xyz[1] != 0.0) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (xyz[2] != 0.0) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of iterator
|
||||||
|
status = arrayDataHandle.next();
|
||||||
|
if (status != MS::kSuccess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pntsArray.destructHandle(dataHandle);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> isEmptyGeometry(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
MFnMesh mesh(dagPath);
|
||||||
|
int numVerts = mesh.numVertices();
|
||||||
|
if (numVerts == 0) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findUnusedVertices(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
int edgeCount;
|
||||||
|
|
||||||
|
for (MItMeshVertex vtxIter(dagPath); !vtxIter.isDone(); vtxIter.next()) {
|
||||||
|
vtxIter.numConnectedEdges(edgeCount);
|
||||||
|
|
||||||
|
if (edgeCount == 0) {
|
||||||
|
createResultString(dagPath, ResultType::Vertex, vtxIter.index(), errorPath);
|
||||||
|
result.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findInstances(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnDagNode fnDag;
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
fnDag.setObject(dagPath);
|
||||||
|
if (fnDag.isInstanced()) {
|
||||||
|
MObject mObj = fnDag.parent(0);
|
||||||
|
MFnDagNode dataParent(mObj);
|
||||||
|
MString instanceSource = dataParent.fullPathName();
|
||||||
|
dagPath.pop(1);
|
||||||
|
MString hierarchyParent = dagPath.fullPathName();
|
||||||
|
if (hierarchyParent != instanceSource) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findConnections(std::vector<std::string>* paths)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
std::vector<std::string> CON_LIST = {
|
||||||
|
"translateX",
|
||||||
|
"translateY",
|
||||||
|
"translateZ",
|
||||||
|
"rotateX",
|
||||||
|
"rotateY",
|
||||||
|
"rotateZ",
|
||||||
|
"rotateOrder",
|
||||||
|
"parentInverseMatrix",
|
||||||
|
"rotatePivot",
|
||||||
|
"rotatePivotTranslate"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < length; i++) {
|
||||||
|
|
||||||
|
MPlugArray plugs;
|
||||||
|
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
dagPath.pop(1);
|
||||||
|
MObject mObj = dagPath.node();
|
||||||
|
MFnDependencyNode fnDep(mObj);
|
||||||
|
fnDep.getConnections(plugs);
|
||||||
|
unsigned int numPlugs = plugs.length();
|
||||||
|
for (unsigned int j=0; j<numPlugs; j++) {
|
||||||
|
MPlug p = plugs[j];
|
||||||
|
MString cn = p.partialName(false, false, false, false, false, true);
|
||||||
|
if (std::find(CON_LIST.begin(), CON_LIST.end(), cn.asChar()) != CON_LIST.end()) {
|
||||||
|
result.push_back(dagPath.fullPathName().asChar());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MeshChecker::MeshChecker()
|
||||||
|
: MPxCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus MeshChecker::doIt(const MArgList& args)
|
||||||
|
{
|
||||||
|
|
||||||
|
MStatus status;
|
||||||
|
MArgDatabase argData(syntax(), args);
|
||||||
|
|
||||||
|
// if argument is not provided use selection list
|
||||||
|
MSelectionList selection;
|
||||||
|
if (args.length() == 0) {
|
||||||
|
MGlobal::getActiveSelectionList(selection);
|
||||||
|
} else {
|
||||||
|
status = argData.getCommandArgument(0, selection);
|
||||||
|
CHECK_MSTATUS_AND_RETURN_IT(status)
|
||||||
|
if (status != MS::kSuccess) {
|
||||||
|
return MStatus::kFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mesh construction
|
||||||
|
MDagPath path;
|
||||||
|
selection.getDagPath(0, path);
|
||||||
|
|
||||||
|
// argument parsing
|
||||||
|
MeshCheckType check_type;
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-check")) {
|
||||||
|
unsigned int check_value;
|
||||||
|
argData.getFlagArgument("-check", 0, check_value);
|
||||||
|
|
||||||
|
check_type = static_cast<MeshCheckType>(check_value);
|
||||||
|
|
||||||
|
// TODO check if exeds value
|
||||||
|
} else {
|
||||||
|
MGlobal::displayError("Check type required.");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> hierarchy;
|
||||||
|
buildHierarchy(path, hierarchy);
|
||||||
|
|
||||||
|
// Number of threads to use
|
||||||
|
size_t numTasks = 8;
|
||||||
|
|
||||||
|
// Split sub-vectors to pass to each thread
|
||||||
|
std::vector<std::vector<std::string>> splitGroups;
|
||||||
|
|
||||||
|
splitGroups.resize(numTasks);
|
||||||
|
size_t n = hierarchy.size() / numTasks + 1;
|
||||||
|
size_t idCounter = 0;
|
||||||
|
for (size_t groupID = 0; groupID < numTasks; groupID++) {
|
||||||
|
splitGroups[groupID].reserve(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t a = 0; a < numTasks; a++) {
|
||||||
|
for (size_t b = 0; b < n; b++) {
|
||||||
|
if (idCounter == hierarchy.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
splitGroups[a].emplace_back(hierarchy[idCounter]);
|
||||||
|
idCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool pool(8);
|
||||||
|
std::vector<std::future<std::vector<std::string>>> results;
|
||||||
|
|
||||||
|
double maxFaceArea { 0.000001 };
|
||||||
|
if (argData.isFlagSet("-maxFaceArea"))
|
||||||
|
argData.getFlagArgument("-maxFaceArea", 0, maxFaceArea);
|
||||||
|
|
||||||
|
double minEdgeLength = 0.000001;
|
||||||
|
if (argData.isFlagSet("-minEdgeLength"))
|
||||||
|
argData.getFlagArgument("-minEdgeLength", 0, minEdgeLength);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < numTasks; i++) {
|
||||||
|
if (check_type == MeshCheckType::TRIANGLES) {
|
||||||
|
results.push_back(pool.enqueue(findTriangles, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::NGONS) {
|
||||||
|
results.push_back(pool.enqueue(findNgons, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::NON_MANIFOLD_EDGES) {
|
||||||
|
results.push_back(pool.enqueue(findNonManifoldEdges, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::LAMINA_FACES) {
|
||||||
|
results.push_back(pool.enqueue(findLaminaFaces, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::BI_VALENT_FACES) {
|
||||||
|
results.push_back(pool.enqueue(findBiValentFaces, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::ZERO_AREA_FACES) {
|
||||||
|
results.push_back(pool.enqueue(findZeroAreaFaces, &splitGroups[i], maxFaceArea));
|
||||||
|
} else if (check_type == MeshCheckType::MESH_BORDER) {
|
||||||
|
results.push_back(pool.enqueue(findMeshBorderEdges, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::CREASE_EDGE) {
|
||||||
|
results.push_back(pool.enqueue(findCreaseEdges, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::ZERO_LENGTH_EDGES) {
|
||||||
|
results.push_back(pool.enqueue(findZeroLengthEdges, &splitGroups[i], minEdgeLength));
|
||||||
|
} else if (check_type == MeshCheckType::UNFROZEN_VERTICES) {
|
||||||
|
results.push_back(pool.enqueue(hasVertexPntsAttr, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::EMPTY_GEOMETRY) {
|
||||||
|
results.push_back(pool.enqueue(isEmptyGeometry, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::UNUSED_VERTICES) {
|
||||||
|
results.push_back(pool.enqueue(findUnusedVertices, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::INSTANCE) {
|
||||||
|
results.push_back(pool.enqueue(findInstances, &splitGroups[i]));
|
||||||
|
} else if (check_type == MeshCheckType::CONNECTIONS) {
|
||||||
|
results.push_back(pool.enqueue(findConnections, &splitGroups[i]));
|
||||||
|
} else {
|
||||||
|
MGlobal::displayError("Invalid check number");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> intermediateResult;
|
||||||
|
|
||||||
|
for (auto&& result : results) {
|
||||||
|
std::vector<std::string> temp = result.get();
|
||||||
|
for (auto& r : temp) {
|
||||||
|
intermediateResult.push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MStringArray outputResult;
|
||||||
|
|
||||||
|
for (std::string& path : intermediateResult) {
|
||||||
|
outputResult.append(path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(outputResult);
|
||||||
|
|
||||||
|
return redoIt();
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus MeshChecker::redoIt()
|
||||||
|
{
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus MeshChecker::undoIt()
|
||||||
|
{
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MeshChecker::isUndoable() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MeshChecker::creator()
|
||||||
|
{
|
||||||
|
return new MeshChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
MSyntax MeshChecker::newSyntax()
|
||||||
|
{
|
||||||
|
MSyntax syntax;
|
||||||
|
syntax.addArg(MSyntax::kString);
|
||||||
|
syntax.addFlag("-c", "-check", MSyntax::kUnsigned);
|
||||||
|
syntax.addFlag("-mfa", "-maxFaceArea", MSyntax::kDouble);
|
||||||
|
syntax.addFlag("-mel", "-minEdgeLength", MSyntax::kDouble);
|
||||||
|
syntax.addFlag("-fix", "-doFix", MSyntax::kBoolean);
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus initializePlugin(MObject mObj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
std::string version_str(pluginVersion);
|
||||||
|
std::string compile_date_str(__DATE__);
|
||||||
|
std::string compile_time_str(__TIME__);
|
||||||
|
std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str);
|
||||||
|
|
||||||
|
MFnPlugin fnPlugin(mObj, pluginAuthor, version.c_str(), "Any");
|
||||||
|
|
||||||
|
status = fnPlugin.registerCommand(pluginCommandName, MeshChecker::creator, MeshChecker::newSyntax);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("registerCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus uninitializePlugin(MObject mObj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
MFnPlugin fnPlugin(mObj);
|
||||||
|
|
||||||
|
status = fnPlugin.deregisterCommand(pluginCommandName);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("deregisterCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
36
meshChecker/src/meshChecker.hpp
Normal file
36
meshChecker/src/meshChecker.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <maya/MPxCommand.h>
|
||||||
|
|
||||||
|
enum class MeshCheckType {
|
||||||
|
TRIANGLES = 0,
|
||||||
|
NGONS,
|
||||||
|
NON_MANIFOLD_EDGES,
|
||||||
|
LAMINA_FACES,
|
||||||
|
BI_VALENT_FACES,
|
||||||
|
ZERO_AREA_FACES,
|
||||||
|
MESH_BORDER,
|
||||||
|
CREASE_EDGE,
|
||||||
|
ZERO_LENGTH_EDGES,
|
||||||
|
UNFROZEN_VERTICES,
|
||||||
|
EMPTY_GEOMETRY,
|
||||||
|
UNUSED_VERTICES,
|
||||||
|
INSTANCE,
|
||||||
|
CONNECTIONS,
|
||||||
|
TEST
|
||||||
|
};
|
||||||
|
|
||||||
|
class MeshChecker final : public MPxCommand {
|
||||||
|
public:
|
||||||
|
static void* creator();
|
||||||
|
static MSyntax newSyntax();
|
||||||
|
|
||||||
|
// command interface
|
||||||
|
MStatus doIt(const MArgList& argList) final;
|
||||||
|
MStatus undoIt() final;
|
||||||
|
MStatus redoIt() final;
|
||||||
|
bool isUndoable() const final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MeshChecker();
|
||||||
|
};
|
15
uvChecker/CMakeLists.txt
Normal file
15
uvChecker/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
project(uvChecker CXX)
|
||||||
|
|
||||||
|
add_maya_library(NAME ${PROJECT_NAME}
|
||||||
|
PRIVATE_SOURCE
|
||||||
|
src/uvChecker.cpp
|
||||||
|
src/uvChecker.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(MAYA_TARGET_TYPE RUNTIME)
|
||||||
|
else ()
|
||||||
|
set(MAYA_TARGET_TYPE LIBRARY)
|
||||||
|
endif()
|
||||||
|
set(INSTALL_DIR ../plug-ins/${MAYA_VERSION})
|
||||||
|
install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR})
|
50
uvChecker/README.md
Normal file
50
uvChecker/README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# UVChecker
|
||||||
|
Find general uv errors
|
||||||
|
|
||||||
|
## Check Numbers
|
||||||
|
|
||||||
|
### 0. Udim border intersections
|
||||||
|
|
||||||
|
![](../images/udimIntersections.png)
|
||||||
|
|
||||||
|
### 1. Unmapped polygon faces
|
||||||
|
|
||||||
|
![](../images/unMappedFaces.png)
|
||||||
|
|
||||||
|
### 2. Zero-area UV faces
|
||||||
|
|
||||||
|
![](../images/zeroAreaUVFaces.png)
|
||||||
|
|
||||||
|
### 3. Unassigned UVs
|
||||||
|
|
||||||
|
![img]()
|
||||||
|
|
||||||
|
### 4. UVs in negative space
|
||||||
|
|
||||||
|
![](../images/UVsInNegative.png)
|
||||||
|
|
||||||
|
### 5. Concave UV faces
|
||||||
|
|
||||||
|
![](../images/concaveUVs.png)
|
||||||
|
|
||||||
|
### 6. Reversed UVs
|
||||||
|
|
||||||
|
![](../images/reversedUVs.png)
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
| Longname | Shortname | Argument types | Default | Properties | Description |
|
||||||
|
|:---------|----------:|:--------------:|:-------:|:----------:|:-----------:|
|
||||||
|
|check|c|integer||C||
|
||||||
|
|uvArea|uva|double|0.000001|C||
|
||||||
|
|uvSet|us|string|current uv set|C|Set what uv set you want to us|
|
||||||
|
|maxUvBorderDistance|muvd|double|0.0|C|Ignore UVs close to udims borders for "Udim border intersections" check|
|
||||||
|
|verbose|v|bool|False|C||
|
||||||
|
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```python
|
||||||
|
from maya import cmds
|
||||||
|
errors = cmds.checkUV("|pSphere1", c=0)
|
||||||
|
print errors
|
||||||
|
>>> [u'|pSphere1|pSphereShape1.map[19]', u'|pSphere1|pSphereShape1.map[20]', ...]
|
||||||
|
```
|
109
uvChecker/python/removeUnassginedUVs.py
Normal file
109
uvChecker/python/removeUnassginedUVs.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""
|
||||||
|
Remove unassigned UVs
|
||||||
|
"""
|
||||||
|
|
||||||
|
from maya import OpenMaya
|
||||||
|
from maya import cmds
|
||||||
|
|
||||||
|
|
||||||
|
def checkUnassignedUVs():
|
||||||
|
|
||||||
|
bads = []
|
||||||
|
|
||||||
|
sel = cmds.ls(sl=True, fl=True, long=True)
|
||||||
|
|
||||||
|
if len(sel) == 0:
|
||||||
|
cmds.error("Nothing is selected")
|
||||||
|
return
|
||||||
|
|
||||||
|
root = sel[0]
|
||||||
|
children = cmds.listRelatives(root, ad=True, type="mesh", fullPath=True)
|
||||||
|
|
||||||
|
sel = OpenMaya.MSelectionList()
|
||||||
|
for i in children:
|
||||||
|
sel.add(i)
|
||||||
|
|
||||||
|
# OpenMaya.MGlobal.getActiveSelectionList(sel)
|
||||||
|
dagPath = OpenMaya.MDagPath()
|
||||||
|
|
||||||
|
for i in range(sel.length()):
|
||||||
|
|
||||||
|
sel.getDagPath(i, dagPath)
|
||||||
|
|
||||||
|
fnMesh = OpenMaya.MFnMesh(dagPath)
|
||||||
|
numAllUVs = fnMesh.numUVs()
|
||||||
|
|
||||||
|
uvCounts = OpenMaya.MIntArray()
|
||||||
|
uvIds = OpenMaya.MIntArray()
|
||||||
|
fnMesh.getAssignedUVs(uvCounts, uvIds)
|
||||||
|
numAssignedUVs = len(set(uvIds))
|
||||||
|
if numAllUVs != numAssignedUVs:
|
||||||
|
bads.append(dagPath.fullPathName())
|
||||||
|
|
||||||
|
return bads
|
||||||
|
|
||||||
|
|
||||||
|
def removeUnassignedUVs():
|
||||||
|
sel = OpenMaya.MSelectionList()
|
||||||
|
OpenMaya.MGlobal.getActiveSelectionList(sel)
|
||||||
|
|
||||||
|
if sel.length() == 0:
|
||||||
|
cmds.error('Nothing is selected')
|
||||||
|
return
|
||||||
|
|
||||||
|
dagPath = OpenMaya.MDagPath()
|
||||||
|
sel.getDagPath(0, dagPath)
|
||||||
|
|
||||||
|
fnMesh = OpenMaya.MFnMesh(dagPath)
|
||||||
|
uArray = OpenMaya.MFloatArray()
|
||||||
|
vArray = OpenMaya.MFloatArray()
|
||||||
|
fnMesh.getUVs(uArray, vArray)
|
||||||
|
|
||||||
|
uvCounts = OpenMaya.MIntArray()
|
||||||
|
uvIds = OpenMaya.MIntArray()
|
||||||
|
fnMesh.getAssignedUVs(uvCounts, uvIds)
|
||||||
|
|
||||||
|
# This is UVs that are actually assgined to the geometry
|
||||||
|
# So you have to keep UVs of those indices.
|
||||||
|
# Then you have to remove all non-assgined UVs(what we call ghost UVs)
|
||||||
|
oldUVIndicesSorted = sorted(list(set(uvIds)))
|
||||||
|
|
||||||
|
# Remap old indices to new clean indices
|
||||||
|
# eg. [x, x, x, 3, x, x, 6, x, x, 9, x, ....] (x means unassigned UVs to be removed)
|
||||||
|
# |
|
||||||
|
# [3, 6, 9, ...] <- Remove Unassgined UVs and pack left UVs
|
||||||
|
# |
|
||||||
|
# [0, 1, 2, ...] <- Remap to clean order
|
||||||
|
|
||||||
|
# Create new dict which key is old uv indices and value is new uv indices
|
||||||
|
# In the above example, { 3:0, 6:1, 9:2, .... }
|
||||||
|
uvMap = {}
|
||||||
|
for num, oldUVIndex in enumerate(oldUVIndicesSorted):
|
||||||
|
uvMap[oldUVIndex] = num
|
||||||
|
|
||||||
|
# Replace old UV indices of each face to new UV indices
|
||||||
|
newUvIds = OpenMaya.MIntArray()
|
||||||
|
for i in uvIds:
|
||||||
|
newUvIds.append(uvMap[i])
|
||||||
|
|
||||||
|
# Construct u and v arrays using only assigned UVs
|
||||||
|
newUs = [uArray[i] for i in oldUVIndicesSorted]
|
||||||
|
newVs = [vArray[i] for i in oldUVIndicesSorted]
|
||||||
|
|
||||||
|
# Convert python new uvArrays from python list to MFloatArray
|
||||||
|
newUArray = OpenMaya.MFloatArray()
|
||||||
|
newVArray = OpenMaya.MFloatArray()
|
||||||
|
for i in newUs:
|
||||||
|
newUArray.append(i)
|
||||||
|
for i in newVs:
|
||||||
|
newVArray.append(i)
|
||||||
|
|
||||||
|
# Clear current uvSet and re-assgin new UV information
|
||||||
|
fnMesh.clearUVs()
|
||||||
|
fnMesh.setUVs(newUArray, newVArray)
|
||||||
|
fnMesh.assignUVs(uvCounts, newUvIds)
|
||||||
|
fnMesh.updateSurface()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
removeUnassignedUVs()
|
545
uvChecker/src/uvChecker.cpp
Normal file
545
uvChecker/src/uvChecker.cpp
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
#include "uvChecker.hpp"
|
||||||
|
#include "../../include/ThreadPool.hpp"
|
||||||
|
#include "../../include/utils.hpp"
|
||||||
|
|
||||||
|
#include <maya/MArgDatabase.h>
|
||||||
|
#include <maya/MArgList.h>
|
||||||
|
#include <maya/MDagPath.h>
|
||||||
|
#include <maya/MFloatArray.h>
|
||||||
|
#include <maya/MFnMesh.h>
|
||||||
|
#include <maya/MFnPlugin.h>
|
||||||
|
#include <maya/MGlobal.h>
|
||||||
|
#include <maya/MItDag.h>
|
||||||
|
#include <maya/MItMeshPolygon.h>
|
||||||
|
#include <maya/MPointArray.h>
|
||||||
|
#include <maya/MSelectionList.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
static const char* const pluginCommandName = "checkUV";
|
||||||
|
static const char* const pluginVersion = "2.1.3";
|
||||||
|
static const char* const pluginAuthor = "Michi Inoue";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<std::string> findUdimIntersections(std::vector<std::string>* paths, const MString uvSet, const double maxUvBorderDistance)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnMesh mesh;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
mesh.setObject(dagPath);
|
||||||
|
std::vector<int> indices;
|
||||||
|
|
||||||
|
for (MItMeshPolygon mItPoly(dagPath); !mItPoly.isDone(); mItPoly.next()) {
|
||||||
|
int vCount = static_cast<int>(mItPoly.polygonVertexCount());
|
||||||
|
int currentUVindex;
|
||||||
|
int nextUVindex;
|
||||||
|
float u1, v1, u2, v2;
|
||||||
|
|
||||||
|
for (int j = 0; j < vCount; j++) {
|
||||||
|
mItPoly.getUVIndex(j, currentUVindex, &uvSet);
|
||||||
|
|
||||||
|
if (j == vCount - 1) {
|
||||||
|
mItPoly.getUVIndex(0, nextUVindex, &uvSet);
|
||||||
|
} else {
|
||||||
|
mItPoly.getUVIndex(j + 1, nextUVindex, &uvSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.getUV(currentUVindex, u1, v1, &uvSet);
|
||||||
|
mesh.getUV(nextUVindex, u2, v2, &uvSet);
|
||||||
|
|
||||||
|
if (floor(u1) == floor(u2) && floor(v1) == floor(v2)) {
|
||||||
|
} else if ((maxUvBorderDistance == 0.0)
|
||||||
|
|| ((fabs(rint(u1) - fabs(u1)) > maxUvBorderDistance)
|
||||||
|
&& (fabs(rint(v1) - fabs(v1)) > maxUvBorderDistance)
|
||||||
|
&& (fabs(rint(u2) - fabs(u2)) > maxUvBorderDistance)
|
||||||
|
&& (fabs(rint(v2) - fabs(v2)) > maxUvBorderDistance))) {
|
||||||
|
indices.push_back(currentUVindex);
|
||||||
|
indices.push_back(nextUVindex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove duplicate elements
|
||||||
|
std::sort(indices.begin(), indices.end());
|
||||||
|
indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
|
||||||
|
|
||||||
|
for (auto& index : indices) {
|
||||||
|
createResultString(dagPath, ResultType::UV, index, errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findNoUvFaces(std::vector<std::string>* paths, const MString uvSet)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
MDagPath dagPath;
|
||||||
|
bool hasUVs;
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) {
|
||||||
|
hasUVs = itPoly.hasUVs(uvSet);
|
||||||
|
if (!hasUVs) {
|
||||||
|
createResultString(dagPath, ResultType::Face, static_cast<int>(itPoly.index()), errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findZeroUvFaces(std::vector<std::string>* paths, const MString uvSet, const double minUVArea)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnMesh mesh;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
mesh.setObject(dagPath);
|
||||||
|
double area;
|
||||||
|
bool hasUVs;
|
||||||
|
for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) {
|
||||||
|
hasUVs = itPoly.hasUVs(uvSet);
|
||||||
|
if (hasUVs) {
|
||||||
|
itPoly.getUVArea(area, &uvSet);
|
||||||
|
if (area < minUVArea) {
|
||||||
|
createResultString(dagPath, ResultType::Face, static_cast<int>(itPoly.index()), errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> hasUnassignedUVs(std::vector<std::string>* paths, const MString uvSet)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnMesh mesh;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
mesh.setObject(dagPath);
|
||||||
|
|
||||||
|
int numUVs = mesh.numUVs(uvSet);
|
||||||
|
MIntArray uvCounts;
|
||||||
|
MIntArray uvIds;
|
||||||
|
mesh.getAssignedUVs(uvCounts, uvIds, &uvSet);
|
||||||
|
unsigned int numUvIds = uvIds.length();
|
||||||
|
|
||||||
|
std::unordered_set<int> uvIdSet;
|
||||||
|
for (unsigned int i = 0; i < numUvIds; i++) {
|
||||||
|
uvIdSet.insert(uvIds[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int numAssignedUVs = static_cast<int>(uvIdSet.size());
|
||||||
|
|
||||||
|
if (numUVs != numAssignedUVs) {
|
||||||
|
result2.push_back(dagPath.fullPathName().asChar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findNegativeSpaceUVs(std::vector<std::string>* paths, const MString uvSet)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnMesh mesh;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
MFloatArray uArray, vArray;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
mesh.setObject(dagPath);
|
||||||
|
mesh.getUVs(uArray, vArray, &uvSet);
|
||||||
|
|
||||||
|
int numUVs = mesh.numUVs(uvSet);
|
||||||
|
|
||||||
|
for (int j = 0; j < numUVs; j++) {
|
||||||
|
float& u = uArray[static_cast<unsigned int>(j)];
|
||||||
|
if (u < 0.0) {
|
||||||
|
createResultString(dagPath, ResultType::UV, j, errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float& v = vArray[static_cast<unsigned int>(j)];
|
||||||
|
if (v < 0.0) {
|
||||||
|
createResultString(dagPath, ResultType::UV, j, errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findConcaveUVs(std::vector<std::string>* paths, const MString uvSet)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
std::vector<int> indices;
|
||||||
|
|
||||||
|
MPointArray points;
|
||||||
|
MIntArray vertexList;
|
||||||
|
|
||||||
|
for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) {
|
||||||
|
unsigned int polygonIndex = itPoly.index();
|
||||||
|
itPoly.getTriangles(points, vertexList);
|
||||||
|
int numVerts = static_cast<int>(itPoly.polygonVertexCount());
|
||||||
|
int p1, p2, p3;
|
||||||
|
bool isReversed = itPoly.isUVReversed();
|
||||||
|
|
||||||
|
for (int i = 0; i < numVerts; i++) {
|
||||||
|
p1 = i;
|
||||||
|
p2 = i + 1;
|
||||||
|
p3 = i + 2;
|
||||||
|
|
||||||
|
if (p2 > numVerts - 1) {
|
||||||
|
p2 -= numVerts;
|
||||||
|
}
|
||||||
|
if (p3 > numVerts - 1) {
|
||||||
|
p3 -= numVerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 uv1;
|
||||||
|
float2 uv2;
|
||||||
|
float2 uv3;
|
||||||
|
|
||||||
|
itPoly.getUV(p1, uv1, &uvSet);
|
||||||
|
itPoly.getUV(p2, uv2, &uvSet);
|
||||||
|
itPoly.getUV(p3, uv3, &uvSet);
|
||||||
|
|
||||||
|
float S = getTriangleArea(uv1[0], uv1[1], uv2[0], uv2[1], uv3[0], uv3[1]);
|
||||||
|
|
||||||
|
if (!isReversed) {
|
||||||
|
if (S <= 0.00000000001) {
|
||||||
|
indices.push_back(static_cast<int>(polygonIndex));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (S >= -0.00000000001) {
|
||||||
|
indices.push_back(static_cast<int>(polygonIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& index : indices) {
|
||||||
|
createResultString(dagPath, ResultType::Face, index, errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> findReversedUVs(std::vector<std::string>* paths, const MString uvSet)
|
||||||
|
{
|
||||||
|
MSelectionList list;
|
||||||
|
|
||||||
|
for (auto& p : *paths) {
|
||||||
|
MString mPath(p.c_str());
|
||||||
|
list.add(mPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int length = list.length();
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
MFnMesh mesh;
|
||||||
|
|
||||||
|
std::vector<std::string> result2;
|
||||||
|
std::string errorPath;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
list.getDagPath(i, dagPath);
|
||||||
|
mesh.setObject(dagPath);
|
||||||
|
|
||||||
|
for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) {
|
||||||
|
if (itPoly.isUVReversed(&uvSet)) {
|
||||||
|
createResultString(dagPath, ResultType::Face, static_cast<int>(itPoly.index()), errorPath);
|
||||||
|
result2.push_back(errorPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
|
UvChecker::UvChecker()
|
||||||
|
: verbose(false)
|
||||||
|
,
|
||||||
|
// uvSet("map1"),
|
||||||
|
minUVArea(0.000001)
|
||||||
|
, maxUvBorderDistance(0.0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UvChecker::~UvChecker()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
MSyntax UvChecker::newSyntax()
|
||||||
|
{
|
||||||
|
MSyntax syntax;
|
||||||
|
syntax.addArg(MSyntax::kString);
|
||||||
|
syntax.addFlag("-v", "-verbose", MSyntax::kBoolean);
|
||||||
|
syntax.addFlag("-c", "-check", MSyntax::kUnsigned);
|
||||||
|
syntax.addFlag("-uva", "-uvArea", MSyntax::kDouble);
|
||||||
|
syntax.addFlag("-us", "-uvSet", MSyntax::kString);
|
||||||
|
syntax.addFlag("-muv", "-maxUvBorderDistance", MSyntax::kDouble);
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus UvChecker::doIt(const MArgList& args)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
MSelectionList sel;
|
||||||
|
|
||||||
|
MArgDatabase argData(syntax(), args);
|
||||||
|
|
||||||
|
status = argData.getCommandArgument(0, sel);
|
||||||
|
|
||||||
|
if (status != MS::kSuccess) {
|
||||||
|
MGlobal::displayError("You have to provide an object path");
|
||||||
|
return MStatus::kFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
MDagPath path;
|
||||||
|
sel.getDagPath(0, path);
|
||||||
|
|
||||||
|
// argument parsing
|
||||||
|
UVCheckType check_type;
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-check")) {
|
||||||
|
unsigned int check_value;
|
||||||
|
argData.getFlagArgument("-check", 0, check_value);
|
||||||
|
|
||||||
|
check_type = static_cast<UVCheckType>(check_value);
|
||||||
|
|
||||||
|
// TODO check if exceeds value
|
||||||
|
} else {
|
||||||
|
MGlobal::displayError("Check type required.");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-verbose"))
|
||||||
|
argData.getFlagArgument("-verbose", 0, verbose);
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-uvArea"))
|
||||||
|
argData.getFlagArgument("-uvArea", 0, minUVArea);
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-uvSet"))
|
||||||
|
argData.getFlagArgument("-uvSet", 0, uvSet);
|
||||||
|
else
|
||||||
|
// mesh.getCurrentUVSetName(uvSet);
|
||||||
|
uvSet = "map1";
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-maxUvBorderDistance"))
|
||||||
|
argData.getFlagArgument("-maxUvBorderDistance", 0, maxUvBorderDistance);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
MString objectPath = "Selected mesh : " + path.fullPathName();
|
||||||
|
MGlobal::displayInfo(objectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> hierarchy;
|
||||||
|
buildHierarchy(path, hierarchy);
|
||||||
|
|
||||||
|
// Number of threads to use
|
||||||
|
size_t numTasks = 8;
|
||||||
|
|
||||||
|
// Split sub-vectors to pass to each thread
|
||||||
|
std::vector<std::vector<std::string>> splitGroups;
|
||||||
|
|
||||||
|
splitGroups.resize(numTasks);
|
||||||
|
size_t n = hierarchy.size() / numTasks + 1;
|
||||||
|
size_t idCounter = 0;
|
||||||
|
for (size_t groupID = 0; groupID < numTasks; groupID++) {
|
||||||
|
splitGroups[groupID].reserve(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t a = 0; a < numTasks; a++) {
|
||||||
|
for (size_t b = 0; b < n; b++) {
|
||||||
|
if (idCounter == hierarchy.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
splitGroups[a].emplace_back(hierarchy[idCounter]);
|
||||||
|
idCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool pool(8);
|
||||||
|
std::vector<std::future<std::vector<std::string>>> results;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < numTasks; i++) {
|
||||||
|
if (check_type == UVCheckType::UDIM) {
|
||||||
|
results.push_back(pool.enqueue(findUdimIntersections, &splitGroups[i], uvSet, maxUvBorderDistance));
|
||||||
|
} else if (check_type == UVCheckType::HAS_UVS) {
|
||||||
|
results.push_back(pool.enqueue(findNoUvFaces, &splitGroups[i], uvSet));
|
||||||
|
} else if (check_type == UVCheckType::ZERO_AREA) {
|
||||||
|
results.push_back(pool.enqueue(findZeroUvFaces, &splitGroups[i], uvSet, minUVArea));
|
||||||
|
} else if (check_type == UVCheckType::UN_ASSIGNED_UVS) {
|
||||||
|
results.push_back(pool.enqueue(hasUnassignedUVs, &splitGroups[i], uvSet));
|
||||||
|
} else if (check_type == UVCheckType::NEGATIVE_SPACE_UVS) {
|
||||||
|
results.push_back(pool.enqueue(findNegativeSpaceUVs, &splitGroups[i], uvSet));
|
||||||
|
} else if (check_type == UVCheckType::CONCAVE_UVS) {
|
||||||
|
results.push_back(pool.enqueue(findConcaveUVs, &splitGroups[i], uvSet));
|
||||||
|
} else if (check_type == UVCheckType::REVERSED_UVS) {
|
||||||
|
results.push_back(pool.enqueue(findReversedUVs, &splitGroups[i], uvSet));
|
||||||
|
} else {
|
||||||
|
MGlobal::displayError("Invalid check number");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> intermediateResult;
|
||||||
|
|
||||||
|
for (auto&& result : results) {
|
||||||
|
std::vector<std::string> temp = result.get();
|
||||||
|
for (auto& r : temp) {
|
||||||
|
intermediateResult.push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MStringArray outputResult;
|
||||||
|
|
||||||
|
for (std::string& path : intermediateResult) {
|
||||||
|
outputResult.append(path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(outputResult);
|
||||||
|
|
||||||
|
return redoIt();
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus UvChecker::redoIt()
|
||||||
|
{
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus UvChecker::undoIt()
|
||||||
|
{
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UvChecker::isUndoable() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* UvChecker::creator()
|
||||||
|
{
|
||||||
|
return new UvChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus initializePlugin(MObject mObj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
std::string version_str(pluginVersion);
|
||||||
|
std::string compile_date_str(__DATE__);
|
||||||
|
std::string compile_time_str(__TIME__);
|
||||||
|
std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str);
|
||||||
|
|
||||||
|
MFnPlugin fnPlugin(mObj, pluginAuthor, version.c_str(), "Any");
|
||||||
|
|
||||||
|
status = fnPlugin.registerCommand(pluginCommandName, UvChecker::creator, UvChecker::newSyntax);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("registerCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus uninitializePlugin(MObject mObj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
MFnPlugin fnPlugin(mObj);
|
||||||
|
status = fnPlugin.deregisterCommand(pluginCommandName);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("deregisterCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
36
uvChecker/src/uvChecker.hpp
Normal file
36
uvChecker/src/uvChecker.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <maya/MPxCommand.h>
|
||||||
|
#include <maya/MString.h>
|
||||||
|
#include <maya/MSyntax.h>
|
||||||
|
|
||||||
|
enum class UVCheckType {
|
||||||
|
UDIM = 0,
|
||||||
|
HAS_UVS,
|
||||||
|
ZERO_AREA,
|
||||||
|
UN_ASSIGNED_UVS,
|
||||||
|
NEGATIVE_SPACE_UVS,
|
||||||
|
CONCAVE_UVS,
|
||||||
|
REVERSED_UVS
|
||||||
|
};
|
||||||
|
|
||||||
|
class UvChecker final : public MPxCommand {
|
||||||
|
public:
|
||||||
|
UvChecker();
|
||||||
|
~UvChecker() final;
|
||||||
|
|
||||||
|
// command interface
|
||||||
|
MStatus doIt(const MArgList& argList) final;
|
||||||
|
MStatus undoIt() final;
|
||||||
|
MStatus redoIt() final;
|
||||||
|
bool isUndoable() const final;
|
||||||
|
|
||||||
|
static void* creator();
|
||||||
|
static MSyntax newSyntax();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool verbose;
|
||||||
|
double minUVArea;
|
||||||
|
MString uvSet;
|
||||||
|
double maxUvBorderDistance;
|
||||||
|
};
|
34
uvOverlapChecker/CMakeLists.txt
Normal file
34
uvOverlapChecker/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
project(findUvOverlaps CXX)
|
||||||
|
|
||||||
|
add_maya_library(NAME ${PROJECT_NAME}
|
||||||
|
PRIVATE_SOURCE
|
||||||
|
src/findUvOverlaps.cpp
|
||||||
|
src/findUvOverlaps.hpp
|
||||||
|
src/bentleyOttmann/bentleyOttmann.cpp
|
||||||
|
src/bentleyOttmann/bentleyOttmann.hpp
|
||||||
|
src/bentleyOttmann/event.hpp
|
||||||
|
src/bentleyOttmann/event.cpp
|
||||||
|
src/bentleyOttmann/lineSegment.hpp
|
||||||
|
src/bentleyOttmann/lineSegment.cpp
|
||||||
|
src/bentleyOttmann/point2D.hpp
|
||||||
|
src/bentleyOttmann/point2D.cpp
|
||||||
|
src/bentleyOttmann/vector2D.hpp
|
||||||
|
src/bentleyOttmann/vector2D.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT APPLE)
|
||||||
|
find_package(OpenMP REQUIRED)
|
||||||
|
target_link_libraries(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
|
OpenMP::OpenMP_CXX
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(MAYA_TARGET_TYPE RUNTIME)
|
||||||
|
else ()
|
||||||
|
set(MAYA_TARGET_TYPE LIBRARY)
|
||||||
|
endif()
|
||||||
|
set(INSTALL_DIR ../plug-ins/${MAYA_VERSION})
|
||||||
|
install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR})
|
30
uvOverlapChecker/README.md
Normal file
30
uvOverlapChecker/README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
## FindUvOverlaps
|
||||||
|
Find overlapped UVs.
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
| Longname | Shortname | Argument types | Default | Properties |
|
||||||
|
|:---------|----------:|:--------------:|:-------:|:----------:|
|
||||||
|
|verbose|v|bool|False|C|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from maya import cmds
|
||||||
|
r = cmds.findUvOverlaps("|pSphere1")
|
||||||
|
print r
|
||||||
|
>>> [u'|pPlane1|pPlaneShape1.map[38]', u'|pPlane1|pPlaneShape1.map[39]', ....]
|
||||||
|
```
|
||||||
|
|
||||||
|
For multiple object check, select multiple objects and just run the command without path argument.
|
||||||
|
|
||||||
|
```python
|
||||||
|
cmds.findUvOverlaps()
|
||||||
|
```
|
||||||
|
|
||||||
|
* Single object selected or object path specified as command argment
|
||||||
|
|
||||||
|
<img src="https://github.com/minoue/CheckTools/blob/media/media/uvOverlaps_single.gif" alt="Image" style="width: 300px;"/>
|
||||||
|
|
||||||
|
* Multiple object selected
|
||||||
|
|
||||||
|
<img src="https://github.com/minoue/CheckTools/blob/media/media/uvOverlaps_multipleObj.gif" alt="Image" style="width: 300px;"/>
|
263
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp
Normal file
263
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
//
|
||||||
|
// bentleyOttmann.cpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "bentleyOttmann.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
BentleyOttmann::BentleyOttmann(std::vector<LineSegment>& edgeVector) : edges(edgeVector)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BentleyOttmann::~BentleyOttmann()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
BentleyOttmann BentleyOttmann::operator+(const BentleyOttmann& rhs) const
|
||||||
|
{
|
||||||
|
// Create new BentleyOttmann object
|
||||||
|
size_t newSize = this->edges.size() + rhs.edges.size();
|
||||||
|
std::vector<LineSegment> AB;
|
||||||
|
AB.reserve(newSize);
|
||||||
|
AB.insert(AB.end(), this->edges.begin(), this->edges.end());
|
||||||
|
AB.insert(AB.end(), rhs.edges.begin(), rhs.edges.end());
|
||||||
|
|
||||||
|
BentleyOttmann newBO(AB);
|
||||||
|
|
||||||
|
return newBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BentleyOttmann::createNewEvent(LineSegment* lineA, LineSegment* lineB)
|
||||||
|
{
|
||||||
|
float x, y;
|
||||||
|
lineUtils::getIntersectionPoint(*lineA, *lineB, x, y);
|
||||||
|
Point2D p(x, y, 0);
|
||||||
|
Event crossEvent(2, lineA, lineB, p);
|
||||||
|
eventQueue.insert(crossEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BentleyOttmann::check(std::vector<LineSegment> &result)
|
||||||
|
{
|
||||||
|
resultPtr = &result;
|
||||||
|
|
||||||
|
for (auto & edge : edges) {
|
||||||
|
Event ev1(0, &edge, edge.begin);
|
||||||
|
Event ev2(1, &edge, edge.end);
|
||||||
|
eventQueue.insert(ev1);
|
||||||
|
eventQueue.insert(ev2);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusPtrQueue.reserve(edges.size());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (eventQueue.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Event firstEvent = *eventQueue.begin();
|
||||||
|
eventQueue.erase(eventQueue.begin());
|
||||||
|
|
||||||
|
switch (firstEvent.eventType) {
|
||||||
|
case Event::BEGIN:
|
||||||
|
doBegin(firstEvent);
|
||||||
|
break;
|
||||||
|
case Event::END:
|
||||||
|
doEnd(firstEvent);
|
||||||
|
break;
|
||||||
|
case Event::CROSS:
|
||||||
|
doCross(firstEvent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BentleyOttmann::doBegin(Event& ev)
|
||||||
|
{
|
||||||
|
LineSegment* currentEdgePtr = ev.edgePtrA;
|
||||||
|
statusPtrQueue.emplace_back(currentEdgePtr);
|
||||||
|
|
||||||
|
if (statusPtrQueue.size() <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set crossing point of Y for all edges in the statusQueue and the sweepline
|
||||||
|
for (auto ePtr : statusPtrQueue) {
|
||||||
|
if (ePtr->isHorizontal) {
|
||||||
|
// If the edge is horizontal, y of corrsing point is always y
|
||||||
|
// as sweepline moves from left to right
|
||||||
|
ePtr->crossingPointY = ePtr->begin.y;
|
||||||
|
} else if (ePtr->isVertical) {
|
||||||
|
// if the edge is vertical, the edge and sweepline are collinear, so
|
||||||
|
// use y of a mid point instread
|
||||||
|
float mid = (ePtr->begin.y + ePtr->end.y) * 0.5F;
|
||||||
|
ePtr->crossingPointY = mid;
|
||||||
|
} else {
|
||||||
|
// otherwise, find y value of crossing point of the edge and sweepline
|
||||||
|
float slope2 = (ePtr->end.y - ePtr->begin.y) / (ePtr->end.x - ePtr->begin.x);
|
||||||
|
float b2 = ePtr->begin.y - slope2 * ePtr->begin.x;
|
||||||
|
float y2 = slope2 * ev.sweepline + b2;
|
||||||
|
ePtr->crossingPointY = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(statusPtrQueue.begin(), statusPtrQueue.end(), EdgeCrossingComparator());
|
||||||
|
|
||||||
|
// StatusQueue was sorted so you have to find the edge added to the queue above and find its index
|
||||||
|
auto foundIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(),
|
||||||
|
currentEdgePtr);
|
||||||
|
if (foundIter == statusPtrQueue.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get currentEdge object from the statusQueue after sorted
|
||||||
|
size_t index = static_cast<size_t>(std::distance(statusPtrQueue.begin(), foundIter));
|
||||||
|
|
||||||
|
if (foundIter == statusPtrQueue.begin()) {
|
||||||
|
LineSegment* targetEdge = statusPtrQueue[index + 1];
|
||||||
|
if (*(*foundIter) * *(targetEdge)) {
|
||||||
|
resultPtr->emplace_back(*(*foundIter));
|
||||||
|
resultPtr->emplace_back(*targetEdge);
|
||||||
|
createNewEvent(currentEdgePtr, targetEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (foundIter == statusPtrQueue.end() - 1) {
|
||||||
|
LineSegment* targetEdge = statusPtrQueue[index - 1];
|
||||||
|
if (*(*foundIter) * *(targetEdge)) {
|
||||||
|
resultPtr->emplace_back(*(*foundIter));
|
||||||
|
resultPtr->emplace_back(*targetEdge);
|
||||||
|
createNewEvent(currentEdgePtr, targetEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LineSegment* nextEdgePtr = statusPtrQueue[index + 1];
|
||||||
|
LineSegment* previousEdgePtr = statusPtrQueue[index - 1];
|
||||||
|
if (*(*foundIter) * *(nextEdgePtr)) {
|
||||||
|
resultPtr->emplace_back(*(*foundIter));
|
||||||
|
resultPtr->emplace_back(*nextEdgePtr);
|
||||||
|
createNewEvent(currentEdgePtr, nextEdgePtr);
|
||||||
|
}
|
||||||
|
if (*(*foundIter) * *(previousEdgePtr)) {
|
||||||
|
resultPtr->emplace_back(*(*foundIter));
|
||||||
|
resultPtr->emplace_back(*previousEdgePtr);
|
||||||
|
createNewEvent(currentEdgePtr, previousEdgePtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BentleyOttmann::doEnd(Event& ev)
|
||||||
|
{
|
||||||
|
LineSegment* currentEdgePtr = ev.edgePtrA;
|
||||||
|
auto foundIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(), currentEdgePtr);
|
||||||
|
|
||||||
|
if (foundIter == statusPtrQueue.end()) {
|
||||||
|
// if iter not found
|
||||||
|
std::cout << "Not found" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundIter == statusPtrQueue.begin() || foundIter == statusPtrQueue.end() - 1) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size_t index = static_cast<size_t>(std::distance(statusPtrQueue.begin(), foundIter));
|
||||||
|
LineSegment* nextEdgePtr = statusPtrQueue[index + 1];
|
||||||
|
LineSegment* previousEdgePtr = statusPtrQueue[index - 1];
|
||||||
|
bool isCrossing = (*nextEdgePtr) * (*previousEdgePtr);
|
||||||
|
if (isCrossing) {
|
||||||
|
resultPtr->emplace_back(*previousEdgePtr);
|
||||||
|
resultPtr->emplace_back(*nextEdgePtr);
|
||||||
|
createNewEvent(nextEdgePtr, previousEdgePtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove current edge from the statusQueue
|
||||||
|
statusPtrQueue.erase(foundIter);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BentleyOttmann::doCross(Event& ev)
|
||||||
|
{
|
||||||
|
if (statusPtrQueue.size() <= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment* edgePtr = ev.edgePtrA;
|
||||||
|
LineSegment* otherEdgePtr = ev.edgePtrB;
|
||||||
|
|
||||||
|
auto lineAPtrIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(),
|
||||||
|
edgePtr);
|
||||||
|
auto lineBPtrIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(),
|
||||||
|
otherEdgePtr);
|
||||||
|
if (lineAPtrIter == statusPtrQueue.end() || lineBPtrIter == statusPtrQueue.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t lineAIndex = static_cast<size_t>(std::distance(statusPtrQueue.begin(), lineAPtrIter));
|
||||||
|
size_t lineBIndex = static_cast<size_t>(std::distance(statusPtrQueue.begin(), lineBPtrIter));
|
||||||
|
size_t small, big;
|
||||||
|
|
||||||
|
if (lineAIndex > lineBIndex) {
|
||||||
|
small = lineBIndex;
|
||||||
|
big = lineAIndex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
small = lineAIndex;
|
||||||
|
big = lineBIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (small == 0) {
|
||||||
|
// Check the first edge and the one after next edge
|
||||||
|
|
||||||
|
// If If the second edge is the last element of the statusQueue, then there is
|
||||||
|
// no edge to be checked with the first edge
|
||||||
|
if (statusPtrQueue.size() == static_cast<size_t>(big) + 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment* lineAPtr = statusPtrQueue[small];
|
||||||
|
LineSegment* lineBPtr = statusPtrQueue[big + 1];
|
||||||
|
bool isCrossing = (*lineAPtr) * (*lineBPtr);
|
||||||
|
if (isCrossing) {
|
||||||
|
resultPtr->emplace_back(*lineAPtr);
|
||||||
|
resultPtr->emplace_back(*lineBPtr);
|
||||||
|
createNewEvent(lineAPtr, lineBPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (big == statusPtrQueue.size() - 1) {
|
||||||
|
// Check the last edge and the one before the previous edge
|
||||||
|
|
||||||
|
LineSegment* lineAPtr = statusPtrQueue[small - 1];
|
||||||
|
LineSegment* lineBPtr = statusPtrQueue[big];
|
||||||
|
bool isCrossing = (*lineAPtr) * (*lineBPtr);
|
||||||
|
if (isCrossing) {
|
||||||
|
resultPtr->emplace_back(*lineAPtr);
|
||||||
|
resultPtr->emplace_back(*lineBPtr);
|
||||||
|
createNewEvent(lineAPtr, lineBPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Check the first edge and the one after next(third)
|
||||||
|
LineSegment* lineAPtr = statusPtrQueue[small - 1];
|
||||||
|
LineSegment* lineBPtr = statusPtrQueue[big];
|
||||||
|
bool isCrossing = (*lineAPtr) * (*lineBPtr);
|
||||||
|
if (isCrossing) {
|
||||||
|
resultPtr->emplace_back(*lineAPtr);
|
||||||
|
resultPtr->emplace_back(*lineBPtr);
|
||||||
|
createNewEvent(lineAPtr, lineBPtr);
|
||||||
|
}
|
||||||
|
// Check the second edge and the one after next(forth)
|
||||||
|
LineSegment* lineCPtr = statusPtrQueue[small];
|
||||||
|
LineSegment* lineDPtr = statusPtrQueue[big + 1];
|
||||||
|
isCrossing = (*lineCPtr) * (*lineDPtr);
|
||||||
|
if (isCrossing) {
|
||||||
|
resultPtr->emplace_back(*lineCPtr);
|
||||||
|
resultPtr->emplace_back(*lineDPtr);
|
||||||
|
createNewEvent(lineCPtr, lineDPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
34
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp
Normal file
34
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// bentleyOttmann.hpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "event.hpp"
|
||||||
|
#include "lineSegment.hpp"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class BentleyOttmann {
|
||||||
|
public:
|
||||||
|
explicit BentleyOttmann(std::vector<LineSegment>& edgeVector);
|
||||||
|
~BentleyOttmann();
|
||||||
|
|
||||||
|
void check(std::vector<LineSegment> &result);
|
||||||
|
std::vector<LineSegment> edges;
|
||||||
|
|
||||||
|
BentleyOttmann operator+(const BentleyOttmann& rhs) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<LineSegment> *resultPtr{};
|
||||||
|
bool doBegin(Event& ev);
|
||||||
|
bool doEnd(Event& ev);
|
||||||
|
bool doCross(Event& ev);
|
||||||
|
void createNewEvent(LineSegment* lineA, LineSegment* lineB);
|
||||||
|
|
||||||
|
std::vector<LineSegment*> statusPtrQueue;
|
||||||
|
std::multiset<Event> eventQueue;
|
||||||
|
};
|
32
uvOverlapChecker/src/bentleyOttmann/event.cpp
Normal file
32
uvOverlapChecker/src/bentleyOttmann/event.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// event.cpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "event.hpp"
|
||||||
|
|
||||||
|
Event::Event() = default;
|
||||||
|
|
||||||
|
Event::Event(int eventType, LineSegment* edgePtrA, const Point2D& point)
|
||||||
|
: eventType(eventType)
|
||||||
|
, edgePtrA(edgePtrA)
|
||||||
|
, eventPoint(point)
|
||||||
|
, sweepline(point.x)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::Event(int eventType, LineSegment* edgePtrA, LineSegment* edgePtrB, const Point2D& point)
|
||||||
|
: eventType(eventType)
|
||||||
|
, edgePtrA(edgePtrA)
|
||||||
|
, edgePtrB(edgePtrB)
|
||||||
|
, eventPoint(point)
|
||||||
|
, sweepline(point.x)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::~Event() = default;
|
||||||
|
|
||||||
|
bool Event::operator<(const Event& rhs) const
|
||||||
|
{
|
||||||
|
return this->eventPoint < rhs.eventPoint;
|
||||||
|
}
|
34
uvOverlapChecker/src/bentleyOttmann/event.hpp
Normal file
34
uvOverlapChecker/src/bentleyOttmann/event.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// event.hptp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lineSegment.hpp"
|
||||||
|
#include "point2D.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
public:
|
||||||
|
Event();
|
||||||
|
Event(int eventType, LineSegment* edgePtrA, const Point2D& point);
|
||||||
|
Event(int eventType, LineSegment* edgePtrA, LineSegment* edgePtrB, const Point2D& point);
|
||||||
|
~Event();
|
||||||
|
|
||||||
|
int eventType{};
|
||||||
|
LineSegment *edgePtrA{}, *edgePtrB{};
|
||||||
|
Point2D eventPoint;
|
||||||
|
int index{};
|
||||||
|
float sweepline{};
|
||||||
|
|
||||||
|
bool operator<(const Event& rhs) const;
|
||||||
|
|
||||||
|
enum EVENT_TYPE {
|
||||||
|
BEGIN,
|
||||||
|
END,
|
||||||
|
CROSS
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
124
uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp
Normal file
124
uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// lineSegment.cpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "lineSegment.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
LineSegment::LineSegment()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment::LineSegment(Point2D p1, Point2D p2, const char* groupId) : groupId(groupId)
|
||||||
|
{
|
||||||
|
if (p1 < p2) {
|
||||||
|
this->begin = p1;
|
||||||
|
this->end = p2;
|
||||||
|
this->index = std::make_pair(p1.index, p2.index);
|
||||||
|
} else {
|
||||||
|
this->begin = p2;
|
||||||
|
this->end = p1;
|
||||||
|
this->index = std::make_pair(p2.index, p1.index);
|
||||||
|
}
|
||||||
|
if (p1.x == p2.x) {
|
||||||
|
this->isVertical = true;
|
||||||
|
this->isHorizontal = false;
|
||||||
|
} else if (p1.y == p2.y) {
|
||||||
|
this->isVertical = false;
|
||||||
|
this->isHorizontal = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->isVertical = false;
|
||||||
|
this->isHorizontal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment::~LineSegment()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineSegment::operator==(const LineSegment& rhs) const
|
||||||
|
{
|
||||||
|
return (this->begin == rhs.begin && this->end == rhs.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
float LineSegment::getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy) const
|
||||||
|
{
|
||||||
|
return ((Ax * (By - Cy)) + (Bx * (Cy - Ay)) + (Cx * (Ay - By))) * 0.5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineSegment::sameSigns(const float x, const float y) const
|
||||||
|
{
|
||||||
|
return (x >= 0) ^ (y < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineSegment::operator*(const LineSegment& rhs) const
|
||||||
|
{
|
||||||
|
// Check if two edges are on a same line
|
||||||
|
float t1 = getTriangleArea(this->begin.x, this->begin.y, rhs.begin.x, rhs.begin.y, this->end.x, this->end.y);
|
||||||
|
float t2 = getTriangleArea(this->begin.x, this->begin.y, rhs.end.x, rhs.end.y, this->end.x, this->end.y);
|
||||||
|
float t3 = getTriangleArea(rhs.begin.x, rhs.begin.y, this->begin.x, this->begin.y, rhs.end.x, rhs.end.y);
|
||||||
|
float t4 = getTriangleArea(rhs.begin.x, rhs.begin.y, this->end.x, this->end.y, rhs.end.x, rhs.end.y);
|
||||||
|
|
||||||
|
if (t1 == 0 && t2 == 0 && t3 == 0 && t4 == 0) {
|
||||||
|
// Two lines are on a same line
|
||||||
|
Vector2D v1 = rhs.end - this->begin;
|
||||||
|
Vector2D v2 = rhs.begin - this->end;
|
||||||
|
v1.normalize();
|
||||||
|
v2.normalize();
|
||||||
|
float dot = v1 * v2;
|
||||||
|
if (dot >= 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t1 * t2 == 0 || t3 * t4 == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ccw1 = sameSigns(t1, t2);
|
||||||
|
bool ccw2 = sameSigns(t3, t4);
|
||||||
|
|
||||||
|
if (ccw1 == false && ccw2 == false) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace lineUtils {
|
||||||
|
void getIntersectionPoint(const LineSegment& lineA, const LineSegment& lineB, float& x, float& y)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Y = AX + B
|
||||||
|
float a1 = (lineA.end.y - lineA.begin.y) / (lineA.end.x - lineA.begin.x);
|
||||||
|
float a2 = (lineB.end.y - lineB.begin.y) / (lineB.end.x - lineB.begin.x);
|
||||||
|
float b1 = lineA.begin.y - (a1 * lineA.begin.x);
|
||||||
|
float b2 = lineB.begin.y - (a2 * lineB.begin.x);
|
||||||
|
|
||||||
|
a1 = -1.0F * a1;
|
||||||
|
a2 = -1.0F * a2;
|
||||||
|
|
||||||
|
// Matrix
|
||||||
|
// | (-a1) 1 || X | = | b1 |
|
||||||
|
// | (-a2) 1 || Y | = | b2 |
|
||||||
|
|
||||||
|
float adbc = a1 - a2;
|
||||||
|
|
||||||
|
// Get inverse matrix
|
||||||
|
|
||||||
|
float a = 1.0F * (1.0F / adbc);
|
||||||
|
float b = -a2 * (1.0F / adbc);
|
||||||
|
float c = -1.0F * (1.0F / adbc);
|
||||||
|
float d = a1 * (1.0F / adbc);
|
||||||
|
|
||||||
|
// [u] = [ a c ] [y_interceptA]
|
||||||
|
// [v] [ b d ] [y_interceptB]
|
||||||
|
x = (a * b1) + (c * b2);
|
||||||
|
y = (b * b1) + (d * b2);
|
||||||
|
}
|
||||||
|
}
|
54
uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp
Normal file
54
uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// lineSegment.hpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "point2D.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
class LineSegment {
|
||||||
|
public:
|
||||||
|
LineSegment();
|
||||||
|
LineSegment(Point2D p1, Point2D p2, const char* groupId);
|
||||||
|
~LineSegment();
|
||||||
|
Point2D begin;
|
||||||
|
Point2D end;
|
||||||
|
float crossingPointY;
|
||||||
|
std::pair<int, int> index;
|
||||||
|
const char* groupId;
|
||||||
|
|
||||||
|
bool operator==(const LineSegment& rhs) const;
|
||||||
|
inline bool operator!=(const LineSegment& rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator*(const LineSegment& rhs) const;
|
||||||
|
|
||||||
|
bool isHorizontal;
|
||||||
|
bool isVertical;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy) const;
|
||||||
|
bool sameSigns(float x, float y) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EdgeCrossingComparator {
|
||||||
|
public:
|
||||||
|
bool operator()(const LineSegment* left, const LineSegment* right) const
|
||||||
|
{
|
||||||
|
if (left->crossingPointY == right->crossingPointY) {
|
||||||
|
return left->end.y < right->end.y;
|
||||||
|
} else {
|
||||||
|
return left->crossingPointY < right->crossingPointY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace lineUtils {
|
||||||
|
void getIntersectionPoint(const LineSegment& lineA, const LineSegment& lineB, float& x, float& y);
|
||||||
|
}
|
88
uvOverlapChecker/src/bentleyOttmann/main.cpp
Normal file
88
uvOverlapChecker/src/bentleyOttmann/main.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// main.cpp
|
||||||
|
// bentleyOttman
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bentleyOttmann.hpp"
|
||||||
|
#include "lineSegment.hpp"
|
||||||
|
#include "point2D.hpp"
|
||||||
|
#include "testData/dataSet.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
int test() {
|
||||||
|
|
||||||
|
std::vector<LineSegment> edgeVec;
|
||||||
|
std::vector<LineSegment> edgeVec2;
|
||||||
|
|
||||||
|
std::string dataPathA = "./testData/DataA-Table_1.csv";
|
||||||
|
std::string dataPathB = "./testData/DataB-Table_1.csv";
|
||||||
|
std::vector<std::vector<std::string> > v1 = TestDataSet::getDataSet(dataPathA);
|
||||||
|
std::vector<std::vector<std::string> > v2 = TestDataSet::getDataSet(dataPathB);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); i++) {
|
||||||
|
int indexA = std::stoi(v1[i][0]);
|
||||||
|
float x1 = std::stof(v1[i][1]);
|
||||||
|
float y1 = std::stof(v1[i][2]);
|
||||||
|
|
||||||
|
int indexB = std::stoi(v1[i][3]);
|
||||||
|
float x2 = std::stof(v1[i][4]);
|
||||||
|
float y2 = std::stof(v1[i][5]);
|
||||||
|
|
||||||
|
Point2D p1(x1, y1, indexA);
|
||||||
|
Point2D p2(x2, y2, indexB);
|
||||||
|
|
||||||
|
LineSegment edge(p1, p2);
|
||||||
|
edgeVec.push_back(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v2.size(); i++) {
|
||||||
|
int indexA = std::stoi(v2[i][0]);
|
||||||
|
float x1 = std::stof(v2[i][1]);
|
||||||
|
float y1 = std::stof(v2[i][2]);
|
||||||
|
|
||||||
|
int indexB = std::stoi(v2[i][3]);
|
||||||
|
float x2 = std::stof(v2[i][4]);
|
||||||
|
float y2 = std::stof(v2[i][5]);
|
||||||
|
|
||||||
|
Point2D p1(x1, y1, indexA);
|
||||||
|
Point2D p2(x2, y2, indexB);
|
||||||
|
|
||||||
|
LineSegment edge(p1, p2);
|
||||||
|
edgeVec2.push_back(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicated line segments
|
||||||
|
std::sort(edgeVec.begin(), edgeVec.end(), EdgeIndexComparator());
|
||||||
|
edgeVec.erase(std::unique(edgeVec.begin(), edgeVec.end()), edgeVec.end());
|
||||||
|
|
||||||
|
std::sort(edgeVec2.begin(), edgeVec2.end(), EdgeIndexComparator());
|
||||||
|
edgeVec.erase(std::unique(edgeVec2.begin(), edgeVec2.end()), edgeVec2.end());
|
||||||
|
|
||||||
|
// Name line group name if necessary
|
||||||
|
std::string groupNameA = "lineGroupA";
|
||||||
|
std::string groupNameB = "lineGroupB";
|
||||||
|
BentleyOttman checker(edgeVec, groupNameA);
|
||||||
|
BentleyOttman checker2(edgeVec2, groupNameB);
|
||||||
|
|
||||||
|
BentleyOttman checker3 = checker + checker2;
|
||||||
|
checker3.check();
|
||||||
|
|
||||||
|
std::vector<LineSegment*>::iterator resultIter;
|
||||||
|
for (resultIter = checker3.resultPtr.begin(); resultIter != checker3.resultPtr.end(); ++resultIter) {
|
||||||
|
const LineSegment& line = *(*resultIter);
|
||||||
|
std::cout << line.begin.index << ":" << line.end.index << " :: " << std::endl;
|
||||||
|
std::cout << line.groupId << std::endl;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[])
|
||||||
|
{
|
||||||
|
test();
|
||||||
|
std::cout << "end" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
43
uvOverlapChecker/src/bentleyOttmann/point2D.cpp
Normal file
43
uvOverlapChecker/src/bentleyOttmann/point2D.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// point2D.cpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "point2D.hpp"
|
||||||
|
|
||||||
|
Point2D::Point2D()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
Point2D::Point2D(float x, float y, int index)
|
||||||
|
: x(x)
|
||||||
|
, y(y)
|
||||||
|
, index(index)
|
||||||
|
, xy(std::make_pair(x, y))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Point2D::~Point2D()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
bool Point2D::operator==(const Point2D& rhs) const
|
||||||
|
{
|
||||||
|
return this->index == rhs.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Point2D::operator<(const Point2D& rhs) const
|
||||||
|
{
|
||||||
|
return this->xy < rhs.xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2D Point2D::operator-(const Point2D& rhs) const
|
||||||
|
{
|
||||||
|
if (this->index == rhs.index) {
|
||||||
|
Vector2D v(0, 0);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
float value_x = rhs.x - this->x;
|
||||||
|
float value_y = rhs.y - this->y;
|
||||||
|
Vector2D v(value_x, value_y);
|
||||||
|
return v;
|
||||||
|
}
|
28
uvOverlapChecker/src/bentleyOttmann/point2D.hpp
Normal file
28
uvOverlapChecker/src/bentleyOttmann/point2D.hpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// point2D.hpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vector2D.hpp"
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
class Point2D {
|
||||||
|
public:
|
||||||
|
Point2D();
|
||||||
|
Point2D(float x, float y, int index);
|
||||||
|
~Point2D();
|
||||||
|
float x{}, y{};
|
||||||
|
int index{};
|
||||||
|
std::pair<float, float> xy;
|
||||||
|
|
||||||
|
bool operator==(const Point2D& rhs) const;
|
||||||
|
inline bool operator!=(const Point2D& rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const Point2D& rhs) const;
|
||||||
|
Vector2D operator-(const Point2D& rhs) const;
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
0,0.0660550594329834,0.0660550594329834,1,0.2830275297164917,0.0660550594329834
|
||||||
|
1,0.2830275297164917,0.0660550594329834,6,0.2830275297164917,0.2830275297164917
|
||||||
|
6,0.2830275297164917,0.2830275297164917,5,0.0660550594329834,0.2830275297164917
|
||||||
|
5,0.0660550594329834,0.2830275297164917,0,0.0660550594329834,0.0660550594329834
|
||||||
|
1,0.2830275297164917,0.0660550594329834,2,0.50,0.0660550594329834
|
||||||
|
2,0.50,0.0660550594329834,7,0.770054817199707,0.36120128631591797
|
||||||
|
7,0.770054817199707,0.36120128631591797,6,0.2830275297164917,0.2830275297164917
|
||||||
|
2,0.50,0.0660550594329834,3,0.7169724702835083,0.0660550594329834
|
||||||
|
3,0.7169724702835083,0.0660550594329834,8,0.7596127390861511,0.23564951121807098
|
||||||
|
8,0.7596127390861511,0.23564951121807098,7,0.770054817199707,0.36120128631591797
|
||||||
|
3,0.7169724702835083,0.0660550594329834,4,0.9292071461677551,0.11106419563293457
|
||||||
|
4,0.9292071461677551,0.11106419563293457,9,0.9339449405670166,0.2830275297164917
|
||||||
|
9,0.9339449405670166,0.2830275297164917,8,0.7596127390861511,0.23564951121807098
|
||||||
|
6,0.2830275297164917,0.2830275297164917,28,0.2830275297164917,0.5
|
||||||
|
28,0.2830275297164917,0.5,29,0.0660550594329834,0.5
|
||||||
|
29,0.0660550594329834,0.5,5,0.0660550594329834,0.2830275297164917
|
||||||
|
7,0.770054817199707,0.36120128631591797,27,0.5,0.5
|
||||||
|
27,0.5,0.5,28,0.2830275297164917,0.5
|
||||||
|
8,0.7596127390861511,0.23564951121807098,25,0.7169724702835083,0.5
|
||||||
|
25,0.7169724702835083,0.5,27,0.5,0.5
|
||||||
|
9,0.9339449405670166,0.2830275297164917,14,0.9339449405670166,0.5
|
||||||
|
14,0.9339449405670166,0.5,25,0.7169724702835083,0.5
|
|
@ -0,0 +1,21 @@
|
|||||||
|
10,-0.5314721465110779,0.6870385408401489,11,-0.5849046111106873,0.8497003316879272
|
||||||
|
11,-0.5849046111106873,0.8497003316879272,16,-0.29304349422454834,0.8890276551246643
|
||||||
|
15,-0.47460252046585083,0.8964254856109619,10,-0.5314721465110779,0.6870385408401489
|
||||||
|
11,-0.5849046111106873,0.8497003316879272,12,-0.1126982569694519,0.5732992887496948
|
||||||
|
12,-0.1126982569694519,0.5732992887496948,17,-0.055828630924224854,0.782686173915863
|
||||||
|
17,-0.055828630924224854,0.782686173915863,16,-0.29304349422454834,0.8890276551246643
|
||||||
|
12,-0.1126982569694519,0.5732992887496948,13,0.09668868780136108,0.5164296627044678
|
||||||
|
13,0.09668868780136108,0.5164296627044678,18,0.19084149599075317,0.6371393203735352
|
||||||
|
18,0.19084149599075317,0.6371393203735352,17,-0.055828630924224854,0.782686173915863
|
||||||
|
13,0.09668868780136108,0.5164296627044678,26,0.3060756325721741,0.45955994725227356
|
||||||
|
26,0.3060756325721741,0.45955994725227356,19,0.3629452586174011,0.6689468026161194
|
||||||
|
19,0.3629452586174011,0.6689468026161194,18,0.19084149599075317,0.6371393203735352
|
||||||
|
16,-0.29304349422454834,0.8890276551246643,21,-0.2083459496498108,1.0489426851272583
|
||||||
|
21,-0.2083459496498108,1.0489426851272583,20,-0.4177328944206238,1.105812430381775
|
||||||
|
20,-0.4177328944206238,1.105812430381775,15,-0.47460252046585083,0.8964254856109619
|
||||||
|
17,-0.055828630924224854,0.782686173915863,22,0.004851400852203369,0.9247608184814453
|
||||||
|
22,0.004851400852203369,0.9247608184814453,21,-0.2083459496498108,1.0489426851272583
|
||||||
|
18,0.19084149599075317,0.6371393203735352,23,0.21042793989181519,0.935203492641449
|
||||||
|
23,0.21042793989181519,0.935203492641449,22,0.004851400852203369,0.9247608184814453
|
||||||
|
19,0.3629452586174011,0.6689468026161194,24,0.4198148846626282,0.8783338665962219
|
||||||
|
24,0.4198148846626282,0.8783338665962219,23,0.21042793989181519,0.935203492641449
|
|
30
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp
Normal file
30
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// dataSet.cpp
|
||||||
|
// bentleyOttman
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "dataSet.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
std::vector<std::vector<std::string> > TestDataSet::getDataSet(std::string path)
|
||||||
|
{
|
||||||
|
std::ifstream ifs(path.c_str());
|
||||||
|
if (!ifs) {
|
||||||
|
std::cout << "Failed to open file" << std::endl;
|
||||||
|
}
|
||||||
|
std::string str;
|
||||||
|
std::vector<std::vector<std::string> > v;
|
||||||
|
|
||||||
|
while (std::getline(ifs, str)) {
|
||||||
|
std::istringstream stream(str);
|
||||||
|
std::string field;
|
||||||
|
std::vector<std::string> result;
|
||||||
|
while (std::getline(stream, field, ',')) {
|
||||||
|
result.push_back(field);
|
||||||
|
}
|
||||||
|
v.push_back(result);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
17
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp
Normal file
17
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// dataSet.hpp
|
||||||
|
// bentleyOttman
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef dataSet_hpp
|
||||||
|
#define dataSet_hpp
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class TestDataSet {
|
||||||
|
public:
|
||||||
|
static std::vector<std::vector<std::string> > getDataSet(std::string path);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* dataSet_hpp */
|
41
uvOverlapChecker/src/bentleyOttmann/vector2D.cpp
Normal file
41
uvOverlapChecker/src/bentleyOttmann/vector2D.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// vector2D.cpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "vector2D.hpp"
|
||||||
|
#include <complex>
|
||||||
|
|
||||||
|
Vector2D::Vector2D()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
Vector2D::Vector2D(float x, float y)
|
||||||
|
: x(x)
|
||||||
|
, y(y)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2D::~Vector2D()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
void Vector2D::normalize()
|
||||||
|
{
|
||||||
|
float m = getLength();
|
||||||
|
if (m != 0) {
|
||||||
|
this->x = this->x / m;
|
||||||
|
this->y = this->y / m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Vector2D::getLength()
|
||||||
|
{
|
||||||
|
float& value_x = this->x;
|
||||||
|
float& value_y = this->y;
|
||||||
|
return std::sqrt(value_x * value_x + value_y * value_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Vector2D::operator*(const Vector2D& rhs) const
|
||||||
|
{
|
||||||
|
float dot = (this->x * rhs.x) + (this->y * rhs.y);
|
||||||
|
return dot;
|
||||||
|
}
|
19
uvOverlapChecker/src/bentleyOttmann/vector2D.hpp
Normal file
19
uvOverlapChecker/src/bentleyOttmann/vector2D.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// vector2D.hpp
|
||||||
|
// bentleyOttmann
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Vector2D {
|
||||||
|
public:
|
||||||
|
Vector2D();
|
||||||
|
Vector2D(float x, float y);
|
||||||
|
~Vector2D();
|
||||||
|
float x{}, y{};
|
||||||
|
|
||||||
|
void normalize();
|
||||||
|
float getLength();
|
||||||
|
|
||||||
|
float operator*(const Vector2D& rhs) const;
|
||||||
|
};
|
392
uvOverlapChecker/src/findUvOverlaps.cpp
Normal file
392
uvOverlapChecker/src/findUvOverlaps.cpp
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include "findUvOverlaps.hpp"
|
||||||
|
#include <maya/MArgDatabase.h>
|
||||||
|
#include <maya/MDagPath.h>
|
||||||
|
#include <maya/MFloatArray.h>
|
||||||
|
#include <maya/MFnMesh.h>
|
||||||
|
#include <maya/MFnPlugin.h>
|
||||||
|
#include <maya/MGlobal.h>
|
||||||
|
#include <maya/MTimer.h>
|
||||||
|
|
||||||
|
static const char* const pluginCommandName = "findUvOverlaps";
|
||||||
|
static const char* const pluginVersion = "1.8.20";
|
||||||
|
static const char* const pluginAuthor = "Michitaka Inoue";
|
||||||
|
|
||||||
|
void UVShell::initAABB()
|
||||||
|
{
|
||||||
|
std::vector<float> uVector;
|
||||||
|
std::vector<float> vVector;
|
||||||
|
for (auto & line : this->lines) {
|
||||||
|
uVector.emplace_back(line.begin.x);
|
||||||
|
uVector.emplace_back(line.end.x);
|
||||||
|
vVector.emplace_back(line.begin.y);
|
||||||
|
vVector.emplace_back(line.end.y);
|
||||||
|
}
|
||||||
|
this->left = *std::min_element(uVector.begin(), uVector.end());
|
||||||
|
this->right = *std::max_element(uVector.begin(), uVector.end());
|
||||||
|
this->bottom = *std::min_element(vVector.begin(), vVector.end());
|
||||||
|
this->top = *std::max_element(vVector.begin(), vVector.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UVShell::operator*(const UVShell& other) const
|
||||||
|
{
|
||||||
|
if (this->right < other.left)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (this->left > other.right)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (this->top < other.bottom)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (this->bottom > other.top)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UVShell UVShell::operator&&(const UVShell& other) const
|
||||||
|
{
|
||||||
|
std::vector<LineSegment> lineVector = this->lines;
|
||||||
|
lineVector.insert(lineVector.end(), other.lines.begin(), other.lines.end());
|
||||||
|
UVShell shell;
|
||||||
|
shell.lines = lineVector;
|
||||||
|
return shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindUvOverlaps::FindUvOverlaps()
|
||||||
|
: verbose(false) {}
|
||||||
|
|
||||||
|
FindUvOverlaps::~FindUvOverlaps() = default;
|
||||||
|
|
||||||
|
void* FindUvOverlaps::creator()
|
||||||
|
{
|
||||||
|
return new FindUvOverlaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
MSyntax FindUvOverlaps::newSyntax()
|
||||||
|
{
|
||||||
|
MSyntax syntax;
|
||||||
|
syntax.addFlag("-v", "-verbose", MSyntax::kBoolean);
|
||||||
|
syntax.addFlag("-set", "-uvSet", MSyntax::kString);
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindUvOverlaps::btoCheck(UVShell& shell)
|
||||||
|
{
|
||||||
|
std::vector<LineSegment> result;
|
||||||
|
BentleyOttmann b(shell.lines);
|
||||||
|
b.check(result);
|
||||||
|
pushToLineVector(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindUvOverlaps::pushToLineVector(std::vector<LineSegment>& v)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
locker.lock();
|
||||||
|
finalResult.push_back(v);
|
||||||
|
locker.unlock();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindUvOverlaps::pushToShellVector(UVShell& shell)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
locker.lock();
|
||||||
|
shellVector.push_back(shell);
|
||||||
|
locker.unlock();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus FindUvOverlaps::doIt(const MArgList& args)
|
||||||
|
{
|
||||||
|
MStatus stat;
|
||||||
|
MTimer timer;
|
||||||
|
double elapsedTime;
|
||||||
|
|
||||||
|
MArgDatabase argData(syntax(), args);
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-verbose"))
|
||||||
|
argData.getFlagArgument("-verbose", 0, verbose);
|
||||||
|
else
|
||||||
|
verbose = false;
|
||||||
|
|
||||||
|
if (argData.isFlagSet("-uvSet"))
|
||||||
|
argData.getFlagArgument("-uvSet", 0, uvSet);
|
||||||
|
else
|
||||||
|
uvSet = "None";
|
||||||
|
|
||||||
|
MGlobal::getActiveSelectionList(mSel);
|
||||||
|
|
||||||
|
timer.beginTimer();
|
||||||
|
// Multithrad obj initialization
|
||||||
|
// std::thread* threadArray = new std::thread[mSel.length()];
|
||||||
|
// for (size_t i = 0; i < mSel.length(); i++) {
|
||||||
|
// threadArray[i] = std::thread(&FindUvOverlaps::init, this, i);
|
||||||
|
// }
|
||||||
|
// for (size_t i = 0; i < mSel.length(); i++) {
|
||||||
|
// threadArray[i].join();
|
||||||
|
// }
|
||||||
|
// delete[] threadArray;
|
||||||
|
|
||||||
|
int numSelected = static_cast<int>(mSel.length());
|
||||||
|
#pragma omp parallel for
|
||||||
|
for (int i = 0; i < numSelected; i++) {
|
||||||
|
init(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.endTimer();
|
||||||
|
elapsedTime = timer.elapsedTime();
|
||||||
|
if (verbose)
|
||||||
|
timeIt("Init time : ", elapsedTime);
|
||||||
|
timer.clear();
|
||||||
|
|
||||||
|
size_t numAllShells = shellVector.size();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < numAllShells; i++) {
|
||||||
|
UVShell& s = shellVector[i];
|
||||||
|
s.initAABB();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<UVShell> shells; // temp countainer to store both original and combined shells
|
||||||
|
|
||||||
|
for (size_t i = 0; i < numAllShells; i++) {
|
||||||
|
UVShell& shellA = shellVector[i];
|
||||||
|
shells.push_back(shellA);
|
||||||
|
|
||||||
|
for (size_t j = i + 1; j < numAllShells; j++) {
|
||||||
|
UVShell& shellB = shellVector[j];
|
||||||
|
|
||||||
|
if (shellA * shellB) {
|
||||||
|
UVShell intersectedShell = shellA && shellB;
|
||||||
|
shells.push_back(intersectedShell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.beginTimer();
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
MString numShellsStr;
|
||||||
|
numShellsStr.set(static_cast<int>(shells.size()));
|
||||||
|
MGlobal::displayInfo("Number of UvShells : " + numShellsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multithread bentleyOttman check
|
||||||
|
size_t numAllShells2 = shells.size();
|
||||||
|
auto* btoThreadArray = new std::thread[numAllShells2];
|
||||||
|
for (size_t i = 0; i < numAllShells2; i++) {
|
||||||
|
UVShell& s = shells[i];
|
||||||
|
btoThreadArray[i] = std::thread(&FindUvOverlaps::btoCheck, this, std::ref(s));
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < numAllShells2; i++) {
|
||||||
|
btoThreadArray[i].join();
|
||||||
|
}
|
||||||
|
delete[] btoThreadArray;
|
||||||
|
|
||||||
|
timer.endTimer();
|
||||||
|
elapsedTime = timer.elapsedTime();
|
||||||
|
if (verbose)
|
||||||
|
timeIt("Check time : ", elapsedTime);
|
||||||
|
timer.clear();
|
||||||
|
|
||||||
|
timer.beginTimer();
|
||||||
|
// Re-insert to set to remove duplicates
|
||||||
|
std::string temp_path;
|
||||||
|
std::unordered_set<std::string> temp;
|
||||||
|
for (auto&& lines : finalResult) {
|
||||||
|
for (auto&& line : lines) {
|
||||||
|
std::string groupName(line.groupId);
|
||||||
|
temp_path = groupName + ".map[" + std::to_string(line.index.first) + "]";
|
||||||
|
temp.insert(temp_path);
|
||||||
|
temp_path = groupName + ".map[" + std::to_string(line.index.second) + "]";
|
||||||
|
temp.insert(temp_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
timer.endTimer();
|
||||||
|
elapsedTime = timer.elapsedTime();
|
||||||
|
if (verbose)
|
||||||
|
timeIt("Removed duplicates : ", elapsedTime);
|
||||||
|
timer.clear();
|
||||||
|
|
||||||
|
// Insert all results to MStringArray for return
|
||||||
|
MString s;
|
||||||
|
MStringArray resultStringArray;
|
||||||
|
|
||||||
|
for (const auto& fullpath : temp) {
|
||||||
|
s.set(fullpath.c_str());
|
||||||
|
resultStringArray.append(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(resultStringArray);
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus FindUvOverlaps::init(int i)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
MDagPath dagPath;
|
||||||
|
mSel.getDagPath(static_cast<unsigned int>(i), dagPath);
|
||||||
|
|
||||||
|
// Check if specified object is geometry or not
|
||||||
|
status = dagPath.extendToShape();
|
||||||
|
if (status != MS::kSuccess) {
|
||||||
|
if (verbose)
|
||||||
|
MGlobal::displayInfo("Failed to extend to shape node.");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dagPath.apiType() != MFn::kMesh) {
|
||||||
|
if (verbose)
|
||||||
|
MGlobal::displayInfo("Selected node : " + dagPath.fullPathName() + " is not mesh. Skipped");
|
||||||
|
return MS::kFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
MFnMesh fnMesh(dagPath);
|
||||||
|
|
||||||
|
// Send to path vector and get pointer to that
|
||||||
|
const char* dagPathChar = paths.emplace_back(dagPath.fullPathName());
|
||||||
|
|
||||||
|
MIntArray uvShellIds;
|
||||||
|
unsigned int nbUvShells;
|
||||||
|
fnMesh.getUvShellsIds(uvShellIds, nbUvShells);
|
||||||
|
|
||||||
|
MIntArray uvCounts; // Num of UVs per face eg. [4, 4, 4, 4, ...]
|
||||||
|
MIntArray uvIds;
|
||||||
|
fnMesh.getAssignedUVs(uvCounts, uvIds);
|
||||||
|
|
||||||
|
unsigned int uvCountSize = uvCounts.length(); // is same as number of faces
|
||||||
|
std::vector<std::pair<unsigned int, unsigned int>> idPairs;
|
||||||
|
idPairs.reserve(uvCountSize * 4);
|
||||||
|
unsigned int uvCounter = 0;
|
||||||
|
unsigned int nextCounter;
|
||||||
|
// Loop over each face and its edges, then create a pair of indices
|
||||||
|
for (unsigned int j = 0; j < uvCountSize; j++) {
|
||||||
|
auto numFaceUVs = static_cast<unsigned int>(uvCounts[j]);
|
||||||
|
for (unsigned int localIndex = 0; localIndex < numFaceUVs; localIndex++) {
|
||||||
|
if (localIndex == numFaceUVs - 1) {
|
||||||
|
// Set the nextCounter to the localIndex of zero of the face
|
||||||
|
nextCounter = uvCounter - numFaceUVs + 1;
|
||||||
|
} else {
|
||||||
|
nextCounter = uvCounter + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idA = uvIds[uvCounter];
|
||||||
|
int idB = uvIds[nextCounter];
|
||||||
|
|
||||||
|
std::pair<unsigned int, unsigned int> idPair;
|
||||||
|
|
||||||
|
if (idA < idB)
|
||||||
|
idPair = std::make_pair(idA, idB);
|
||||||
|
else
|
||||||
|
idPair = std::make_pair(idB, idA);
|
||||||
|
|
||||||
|
idPairs.emplace_back(idPair);
|
||||||
|
uvCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicate elements
|
||||||
|
std::sort(idPairs.begin(), idPairs.end());
|
||||||
|
idPairs.erase(std::unique(idPairs.begin(), idPairs.end()), idPairs.end());
|
||||||
|
|
||||||
|
// Temp countainer for lineSegments for each UVShell
|
||||||
|
std::vector<std::vector<LineSegment>> edgeVector;
|
||||||
|
edgeVector.resize(nbUvShells);
|
||||||
|
|
||||||
|
MFloatArray uArray;
|
||||||
|
MFloatArray vArray;
|
||||||
|
fnMesh.getUVs(uArray, vArray);
|
||||||
|
|
||||||
|
// Setup uv shell objects
|
||||||
|
std::vector<UVShell> shells(nbUvShells);
|
||||||
|
|
||||||
|
// Loop over all id pairs and create lineSegment objects
|
||||||
|
for (auto & idPair : idPairs) {
|
||||||
|
|
||||||
|
unsigned int idA = idPair.first;
|
||||||
|
unsigned int idB = idPair.second;
|
||||||
|
Point2D p1(uArray[idA], vArray[idA], static_cast<int>(idA));
|
||||||
|
Point2D p2(uArray[idB], vArray[idB], static_cast<int>(idB));
|
||||||
|
|
||||||
|
// Create new lineSegment object
|
||||||
|
LineSegment line(p1, p2, dagPathChar);
|
||||||
|
|
||||||
|
auto shellIndex = static_cast<unsigned int>(uvShellIds[idA]);
|
||||||
|
shells[shellIndex].lines.emplace_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & shell : shells) {
|
||||||
|
pushToShellVector(shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MS::kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindUvOverlaps::timeIt(const std::string& text, double t)
|
||||||
|
{
|
||||||
|
MString message, time;
|
||||||
|
message.set(text.c_str());
|
||||||
|
time.set(t);
|
||||||
|
MGlobal::displayInfo(message + " : " + time + " seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The following routines are used to register/unregister
|
||||||
|
// the command we are creating within Maya
|
||||||
|
//
|
||||||
|
|
||||||
|
MStatus initializePlugin(MObject obj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
|
||||||
|
std::string version_str(pluginVersion);
|
||||||
|
std::string compile_date_str(__DATE__);
|
||||||
|
std::string compile_time_str(__TIME__);
|
||||||
|
std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str);
|
||||||
|
|
||||||
|
MFnPlugin plugin(obj, pluginAuthor, version.c_str(), "Any");
|
||||||
|
|
||||||
|
status = plugin.registerCommand(pluginCommandName, FindUvOverlaps::creator, FindUvOverlaps::newSyntax);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("registerCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
MStatus uninitializePlugin(MObject obj)
|
||||||
|
{
|
||||||
|
MStatus status;
|
||||||
|
MFnPlugin plugin(obj);
|
||||||
|
|
||||||
|
status = plugin.deregisterCommand(pluginCommandName);
|
||||||
|
if (!status) {
|
||||||
|
status.perror("deregisterCommand");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *MStringVector::emplace_back(const MString& path) {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
elements.emplace_back(path);
|
||||||
|
MString &tempMstring = elements.back();
|
||||||
|
const char* tempChar = tempMstring.asChar();
|
||||||
|
return tempChar;
|
||||||
|
}
|
57
uvOverlapChecker/src/findUvOverlaps.hpp
Normal file
57
uvOverlapChecker/src/findUvOverlaps.hpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "bentleyOttmann/bentleyOttmann.hpp"
|
||||||
|
#include "bentleyOttmann/lineSegment.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <maya/MString.h>
|
||||||
|
#include <maya/MArgList.h>
|
||||||
|
#include <maya/MSyntax.h>
|
||||||
|
#include <maya/MPxCommand.h>
|
||||||
|
#include <maya/MSelectionList.h>
|
||||||
|
|
||||||
|
class UVShell {
|
||||||
|
float left, right, top, bottom;
|
||||||
|
public:
|
||||||
|
std::vector<LineSegment> lines;
|
||||||
|
void initAABB();
|
||||||
|
bool operator*(const UVShell& other) const;
|
||||||
|
UVShell operator&&(const UVShell& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MStringVector {
|
||||||
|
private:
|
||||||
|
std::mutex mtx;
|
||||||
|
std::vector<MString> elements;
|
||||||
|
public:
|
||||||
|
const char* emplace_back(const MString& path);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FindUvOverlaps : public MPxCommand {
|
||||||
|
public:
|
||||||
|
FindUvOverlaps();
|
||||||
|
~FindUvOverlaps() override;
|
||||||
|
|
||||||
|
MStatus doIt(const MArgList& args) override;
|
||||||
|
|
||||||
|
static void* creator();
|
||||||
|
static MSyntax newSyntax();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex locker;
|
||||||
|
MString uvSet;
|
||||||
|
bool verbose;
|
||||||
|
MSelectionList mSel;
|
||||||
|
MStringVector paths;
|
||||||
|
|
||||||
|
std::vector<std::vector<LineSegment> > finalResult;
|
||||||
|
std::vector<UVShell> shellVector;
|
||||||
|
|
||||||
|
MStatus init(int i);
|
||||||
|
void btoCheck(UVShell &shell);
|
||||||
|
void pushToLineVector(std::vector<LineSegment> &v);
|
||||||
|
void pushToShellVector(UVShell &shell);
|
||||||
|
static void timeIt(const std::string& text, double t);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user