diff options
| author | Marijn Tamis <[email protected]> | 2018-05-03 18:22:48 +0200 |
|---|---|---|
| committer | Marijn Tamis <[email protected]> | 2018-05-03 18:22:48 +0200 |
| commit | ca32c59a58d37c1822e185a2d5f3d0d3e8943593 (patch) | |
| tree | b06b9eec03f34344ef8fc31aa147b2714d3962ee /NvCloth/samples/SampleBase/utils | |
| parent | Forced rename of platform folders in cmake dir. Git didn't pick this up before. (diff) | |
| download | nvcloth-ca32c59a58d37c1822e185a2d5f3d0d3e8943593.tar.xz nvcloth-ca32c59a58d37c1822e185a2d5f3d0d3e8943593.zip | |
NvCloth 1.1.4 Release. (24070740)
Diffstat (limited to 'NvCloth/samples/SampleBase/utils')
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.cpp | 102 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.h | 26 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp | 217 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h | 21 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/DataStream.cpp | 67 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/DataStream.h | 72 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/JobManager.cpp | 19 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/JobManager.h | 30 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/MeshGenerator.cpp | 353 | ||||
| -rw-r--r-- | NvCloth/samples/SampleBase/utils/MeshGenerator.h | 9 |
10 files changed, 837 insertions, 79 deletions
diff --git a/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.cpp b/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.cpp new file mode 100644 index 0000000..9bb15f8 --- /dev/null +++ b/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.cpp @@ -0,0 +1,102 @@ +/* +* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, related documentation +* and any modifications thereto. Any use, reproduction, disclosure or +* distribution of this software and related documentation without an express +* license agreement from NVIDIA CORPORATION is strictly prohibited. +*/ + + +#include "DataStream.h" +#include "renderer/Model.h" +#include "scene/Scene.h" +#include "utils/DebugLineRenderBuffer.h" +#include <map> + +bool saveBoneCapsuleData(std::string filepath, Model* model, + std::vector<physx::PxVec4>& sphereOffsets, std::vector<uint32_t>& activeSpheres, std::vector<uint32_t>& capsuleNodes) +{ + DataStream stream; + uint32_t sphereOffsetCount = (uint32_t)sphereOffsets.size(); + stream.write(sphereOffsetCount); + for(int i = 0; i < (int)sphereOffsets.size(); i++) + { + stream.write(std::string(model->getNodeName(i))); + stream.write(sphereOffsets[i]); + } + uint32_t activeSphereCount = (uint32_t)activeSpheres.size(); + stream.write(activeSphereCount); + for(int i = 0; i < (int)activeSpheres.size(); i++) + { + stream.write(activeSpheres[i]); + } + uint32_t capsuleNodeCount = (uint32_t)capsuleNodes.size(); + stream.write(capsuleNodeCount); + for(int i = 0; i < (int)capsuleNodes.size(); i++) + { + stream.write(capsuleNodes[i]); + } + + stream.saveToFile(filepath.c_str()); + return true; +} + +bool loadBoneCapsuleData(std::string filepath, Model* model, + std::vector<physx::PxVec4>& sphereOffsets, std::vector<uint32_t>& activeSpheres, std::vector<uint32_t>& capsuleNodes) +{ + if(!DataStream::fileExists(filepath.c_str())) + return false; + + assert(sphereOffsets.size() == model->getNodeCount()); + + DataStream stream(filepath.c_str()); + std::map<int, int> fileNodeIdToModelNodeId; + uint32_t sphereOffsetCount = stream.read<uint32_t>(); + for(int i = 0; i < (int)sphereOffsetCount; i++) + { + auto nodeName = stream.read<std::string>(); + int modelNode = model->getNodeIdByNameWithErrorCode(nodeName); + fileNodeIdToModelNodeId[i] = modelNode; + if(modelNode == -1) + { + stream.read<physx::PxVec4>(); + printf("Warning: node [%d] \"%s\" not found in model (%s)\n", i, nodeName.c_str(), filepath.c_str()); + continue; + } + sphereOffsets[modelNode] = stream.read<physx::PxVec4>(); + } + activeSpheres.resize(stream.read<uint32_t>()); + for(int i = 0; i < (int)activeSpheres.size(); i++) + { + int fileNode = stream.read<uint32_t>(); + if(fileNodeIdToModelNodeId[fileNode] == -1) + continue; + activeSpheres[i] = fileNodeIdToModelNodeId[fileNode]; + } + capsuleNodes.resize(stream.read<uint32_t>()); + for(int i = 0; i < (int)capsuleNodes.size(); i++) + { + int fileNode = stream.read<uint32_t>(); + if(fileNodeIdToModelNodeId[fileNode] == -1) + continue; + capsuleNodes[i] = fileNodeIdToModelNodeId[fileNode]; + } + return true; +} + +void renderBoneLines(Scene* scene, Model* model, ModelInstance* instance, physx::PxMat44 transform, float scale) +{ + DebugLineRenderBuffer* dbl = scene->getSceneController()->getDebugLineRenderBuffer(); + + //Render bone lines + model->traverseNodes([&transform, scale, instance, dbl](int nodeId, int parrentId) + { + physx::PxVec3 a = instance->mNodes[parrentId].mTransform.transform(physx::PxVec3(0.0f, 0.0f, 0.0f)); + physx::PxVec3 b = instance->mNodes[nodeId].mTransform.transform(physx::PxVec3(0.0f, 0.0f, 0.0f)); + a = transform.transform(a * scale); + b = transform.transform(b * scale); + dbl->addLine(a, b, 0xFFFFFFFF); + }); +}
\ No newline at end of file diff --git a/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.h b/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.h new file mode 100644 index 0000000..efe04b2 --- /dev/null +++ b/NvCloth/samples/SampleBase/utils/AnimatedModelUtilities.h @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, related documentation +* and any modifications thereto. Any use, reproduction, disclosure or +* distribution of this software and related documentation without an express +* license agreement from NVIDIA CORPORATION is strictly prohibited. +*/ + +#pragma once +#include <vector> +#include <string> +#include <PxVec4.h> +#include <PxVec3.h> +#include <PxMat44.h> +class Model; +class Scene; + +bool saveBoneCapsuleData(std::string filepath, Model* model, + std::vector<physx::PxVec4>& sphereOffsets, std::vector<uint32_t>& activeSpheres, std::vector<uint32_t>& capsuleNodes); + +bool loadBoneCapsuleData(std::string filepath, Model* model, + std::vector<physx::PxVec4>& sphereOffsets, std::vector<uint32_t>& activeSpheres, std::vector<uint32_t>& capsuleNodes); + +void renderBoneLines(Scene* scene, Model* model, ModelInstance* instance, physx::PxMat44 transform = physx::PxMat44(physx::PxIdentity), float scale = 1.0f);
\ No newline at end of file diff --git a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp index 495ba15..a0c0d3a 100644 --- a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp +++ b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp @@ -10,6 +10,25 @@ #include "ClothMeshGenerator.h" +#include "PsMathUtils.h" + +#include <fstream> +#include <iterator> +#include <algorithm> +#include <assert.h> + +namespace +{ + template<typename T> + std::vector<T> readValuesFromFile(const std::string& path) + { + std::ifstream inputFile(path); + std::vector<T> data{ std::istream_iterator<T>{inputFile}, { } }; + return std::move(data); + } +} // end of anonymous namespace + + void ClothMeshData::Clear() { mVertices.clear(); @@ -34,8 +53,6 @@ GeneratePlaneCloth(x,y,2,2) generates: */ -// Submesh submesh; - Clear(); mVertices.resize((segmentsX + 1) * (segmentsY + 1)); mInvMasses.resize((segmentsX + 1) * (segmentsY + 1)); @@ -144,6 +161,90 @@ GeneratePlaneCloth(x,y,2,2) generates: } } +void ClothMeshData::GenerateCylinderWave(float radiusTop, float radiusBottom, float height, float frequency, float ampitudeTop, float ampitudeBottom, int segmentsX, int segmentsY, physx::PxMat44 transform, bool attachTop, bool attachBottom, bool createQuads, int missingXsegments) +{ + Clear(); + int particleXsegments = segmentsX - std::max(0, missingXsegments - 1); + int triangleXsegments = segmentsX - missingXsegments; + assert(missingXsegments < segmentsX); + mVertices.resize((particleXsegments + 0) * (segmentsY + 1)); + mInvMasses.resize((particleXsegments + 0) * (segmentsY + 1)); + mTriangles.resize(triangleXsegments * segmentsY * 2); + if (createQuads) + mQuads.resize(triangleXsegments * segmentsY); + + mMesh.vertices.resize(mVertices.size()); + mMesh.indices.resize(3 * mTriangles.size()); + + float slopeX; + float slopeY; + { + float y = height; + float x = radiusBottom - radiusTop; + float l = sqrtf(x*x + y*y); + slopeY = x / l; + slopeX = y / l; + } + + // Vertices + for (int y = 0; y < segmentsY + 1; y++) + { + float h = height - (float)y / (float)segmentsY * height - 0.5f*height; + float ynorm = (float)y / (float)(segmentsY - 1); + float w = ynorm; + float r = radiusBottom * w + (1.0f - w) * radiusTop; + for (int x = 0; x < particleXsegments; x++) + { + float theta = (float)x / (float)segmentsX * physx::PxTwoPi; + float rw = r + cosf(frequency*theta)*(ampitudeBottom * w + (1.0f - w) * ampitudeTop); + mVertices[x + y * particleXsegments] = transform.transform(physx::PxVec3(sinf(theta)*rw, h, cosf(theta)*rw)); + mInvMasses[x + y * particleXsegments] = (y == 0 && attachTop || y == segmentsY && attachBottom) ? 0.0f : 1.0f; + + mMesh.vertices[x + y * particleXsegments].position = mVertices[x + y * particleXsegments]; + mMesh.vertices[x + y * particleXsegments].uv = physx::PxVec2((float)x / (float)particleXsegments, (float)y / (float)segmentsY); + // Not the correct normal, but we recalculate it anyway when updating the cloth mesh + mMesh.vertices[x + y * particleXsegments].normal = physx::PxVec3(cosf(theta)*slopeX, slopeY, -sinf(theta)*slopeX); + } + } + + if (createQuads) + { + // Quads + for (int y = 0; y < segmentsY; y++) + { + for (int x = 0; x < triangleXsegments; x++) + { + mQuads[(x + y * triangleXsegments)] = Quad((uint32_t)(x + 0) + (y + 0) * (particleXsegments), + (uint32_t)((x + 1) % particleXsegments) + (y + 0) * (particleXsegments), + (uint32_t)((x + 1) % particleXsegments) + (y + 1) * (particleXsegments), + (uint32_t)((x + 0) % particleXsegments) + (y + 1) * (particleXsegments)); + } + } + } + + // Triangles + for (int y = 0; y < segmentsY; y++) + { + for (int x = 0; x < triangleXsegments; x++) + { + mTriangles[(x + y * triangleXsegments) * 2 + 0] = Triangle((uint32_t)((x + 1) % particleXsegments) + (y + 1) * (particleXsegments), + (uint32_t)((x + 1) % particleXsegments) + (y + 0) * (particleXsegments), + (uint32_t)(x + 0) + (y + 0) * (particleXsegments)); + + mTriangles[(x + y * triangleXsegments) * 2 + 1] = Triangle((uint32_t)((x + 0) % particleXsegments) + (y + 1) * (particleXsegments), + (uint32_t)((x + 1) % particleXsegments) + (y + 1) * (particleXsegments), + (uint32_t)(x + 0) + (y + 0) * (particleXsegments)); + } + } + + for (int i = 0; i < (int)mTriangles.size(); i++) + { + mMesh.indices[3 * i + 0] = mTriangles[i].a; + mMesh.indices[3 * i + 1] = mTriangles[i].b; + mMesh.indices[3 * i + 2] = mTriangles[i].c; + } +} + void ClothMeshData::AttachClothPlaneByAngles(int segmentsX, int segmentsY, bool attachByWidth) { for (int y = 0; y < segmentsY + 1; y++) @@ -161,6 +262,105 @@ void ClothMeshData::AttachClothPlaneBySide(int segmentsX, int segmentsY, bool at mInvMasses[x + y * (segmentsX + 1)] = 0.0f; } +void ClothMeshData::AttachClothUsingTopVertices(float thresholdY) +{ + int topVertexIndex = -1; + physx::PxVec3 topVertex(-1e30f, -1e30f, -1e30f); + + for (int i = 0; i < (int)mVertices.size(); ++i) + { + if (mVertices[i].y > topVertex.y) + { + topVertex = mVertices[i]; + topVertexIndex = i; + } + } + NV_CLOTH_ASSERT(topVertexIndex >= 0); + + for (int i = 0; i < (int)mVertices.size(); ++i) + { + if (topVertex.y - mVertices[i].y < thresholdY) + { + mInvMasses[i] = 0.0f; + } + } +} + +bool ClothMeshData::ReadClothFromFile(const std::string& verticesPath, const std::string& indicesPath, physx::PxMat44 transform) +{ + std::vector<float> verticesXYZ = readValuesFromFile<float>(verticesPath); + std::vector<uint32_t> indices = readValuesFromFile<uint32_t>(indicesPath); + + if(verticesXYZ.size() < 3*3 || indices.size() < 3) + return false; + + return InitializeFromData<float,uint32_t>(ToBoundedData(verticesXYZ), ToBoundedData(indices), transform); +} + +template<typename PositionType, typename IndexType> +bool ClothMeshData::InitializeFromData(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform) +{ + if(positions.count < 3 || indices.count < 3) + return false; + + NV_CLOTH_ASSERT(sizeof(PositionType) != physx::PxVec3 || positions.count % 3 == 0); + NV_CLOTH_ASSERT(indices.count % 3 == 0); + + auto numVertices = (sizeof(PositionType) == sizeof(physx::PxVec3)) ? positions.count : positions.count / 3; + const auto numTriangles = indices.count / 3; + + Clear(); + mVertices.resize(numVertices); + mInvMasses.resize(numVertices); + mTriangles.resize(numTriangles); + + // Quads not supported yet + //mQuads.resize(numTriangles / 2); + + mMesh.vertices.resize(mVertices.size()); + mMesh.indices.resize(3 * mTriangles.size()); + + for(int i = 0; i < (int)numVertices; ++i) + { + physx::PxVec3 pos; + if(sizeof(PositionType) == sizeof(physx::PxVec3)) + pos = positions.at<physx::PxVec3>(i); + else + pos = physx::PxVec3(positions.at<float>(i * 3), positions.at<float>(i * 3 + 1), positions.at<float>(i * 3 + 2)); + + pos = transform.transform(pos); + + mVertices[i] = pos; + mInvMasses[i] = 1.0f; + + mMesh.vertices[i].position = pos; + mMesh.vertices[i].normal = transform.transform(physx::PxVec3(0.f, 1.f, 0.f)); // TODO + mMesh.vertices[i].uv = physx::PxVec2(0.0f, 0.0f); // TODO + } + + for(int i = 0; i < (int)numTriangles; ++i) + { + mTriangles[i] = Triangle( + indices.at<IndexType>(i * 3), + indices.at<IndexType>(i * 3 + 1), + indices.at<IndexType>(i * 3 + 2) + ); + } + + for(int i = 0; i < (int)numTriangles; i++) + { + mMesh.indices[3 * i + 0] = mTriangles[i].a; + mMesh.indices[3 * i + 1] = mTriangles[i].b; + mMesh.indices[3 * i + 2] = mTriangles[i].c; + } + + return true; +} +template bool ClothMeshData::InitializeFromData<float,uint16_t>(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform); +template bool ClothMeshData::InitializeFromData<float,uint32_t>(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform); +template bool ClothMeshData::InitializeFromData<physx::PxVec3,uint16_t>(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform); +template bool ClothMeshData::InitializeFromData<physx::PxVec3,uint32_t>(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform); + void ClothMeshData::SetInvMasses(float invMass) { // Doesn't modify attached vertices @@ -178,17 +378,6 @@ void ClothMeshData::SetInvMassesFromDensity(float density) mInvMasses[i] = 1.f / density; } -template <typename T> -nv::cloth::BoundedData ToBoundedData(T& vector) -{ - nv::cloth::BoundedData d; - d.data = &vector[0]; - d.stride = sizeof(vector[0]); - d.count = (physx::PxU32)vector.size(); - - return d; -} - nv::cloth::ClothMeshDesc ClothMeshData::GetClothMeshDesc() { nv::cloth::ClothMeshDesc d; @@ -218,7 +407,7 @@ void ClothMeshData::Merge(const ClothMeshData& other) mUvs.insert(mUvs.end(), other.mUvs.begin(), other.mUvs.end()); mInvMasses.insert(mInvMasses.end(), other.mInvMasses.begin(), other.mInvMasses.end()); - mMesh.vertices.insert(mMesh.vertices.end(), mMesh.vertices.begin(), mMesh.vertices.end()); + mMesh.vertices.insert(mMesh.vertices.end(), other.mMesh.vertices.begin(), other.mMesh.vertices.end()); for(const auto& t : other.mTriangles) { diff --git a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h index 1db4115..a765fee 100644 --- a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h +++ b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h @@ -18,6 +18,17 @@ struct ClothMeshData { + template <typename T> + static nv::cloth::BoundedData ToBoundedData(T& vector) + { + nv::cloth::BoundedData d; + d.data = &vector[0]; + d.stride = sizeof(vector[0]); + d.count = (physx::PxU32)vector.size(); + + return d; + } + struct Triangle { Triangle(){} @@ -46,10 +57,18 @@ struct ClothMeshData void Clear(); void GeneratePlaneCloth(float width, float height, int segmentsX, int segmentsY, bool createQuads = false, physx::PxMat44 transform = physx::PxIdentity, bool alternatingDiagonals = true, int zigzag = 0); - + void GenerateCylinderWave(float radiusTop, float radiusBottom, float height, float frequency, float ampitudeTop, float ampitudeBottom, int segmentsX, int segmentsY, physx::PxMat44 transform = physx::PxIdentity, bool attachTop = false, bool attachBottom = false, bool createQuads = false, int missingXsegments = 0); void AttachClothPlaneByAngles(int segmentsX, int segmentsY, bool attachByWidth = true); void AttachClothPlaneBySide(int segmentsX, int segmentsY, bool attachByWidth = true); + bool ReadClothFromFile(const std::string& verticesPath, const std::string& indicesPath, physx::PxMat44 transform = physx::PxIdentity); + + //positions as float (3 elements per position) + template<typename PositionType = float, typename IndexType = uint16_t> + bool InitializeFromData(nv::cloth::BoundedData positions, nv::cloth::BoundedData indices, physx::PxMat44 transform = physx::PxMat44(physx::PxIdentity)); + + void AttachClothUsingTopVertices(float thresholdY = 0.5f); + void SetInvMasses(float invMass); void SetInvMassesFromDensity(float density); // Todo diff --git a/NvCloth/samples/SampleBase/utils/DataStream.cpp b/NvCloth/samples/SampleBase/utils/DataStream.cpp new file mode 100644 index 0000000..c61badc --- /dev/null +++ b/NvCloth/samples/SampleBase/utils/DataStream.cpp @@ -0,0 +1,67 @@ +/* +* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, related documentation +* and any modifications thereto. Any use, reproduction, disclosure or +* distribution of this software and related documentation without an express +* license agreement from NVIDIA CORPORATION is strictly prohibited. +*/ + +#include "DataStream.h" +#include <fstream> + +DataStream::DataStream() + : + mReadPos(0), + mWritePos(0) +{ + +} +DataStream::DataStream(void* data, int sizeInBytes) + : + mReadPos(0), + mWritePos(0) +{ + write(data, sizeInBytes); +} +DataStream::DataStream(const char* filename) + : + mReadPos(0), + mWritePos(0) +{ + std::ifstream file; + file.open(filename,std::ifstream::binary | std::ios::ate); + assert(file.good()); + + std::streamsize size = file.tellg(); + mBuffer.resize(size); + file.seekg(0, std::ios::beg); + file.read(reinterpret_cast<char*>(mBuffer.data()), size); + + mWritePos = size; + file.close(); +} + +bool DataStream::fileExists(const char* filename) +{ + std::ifstream file; + file.open(filename, std::ifstream::binary | std::ios::ate); + return file.good(); +} + +void DataStream::saveToFile(const char* filename) +{ + std::ofstream file(filename, std::fstream::binary | std::fstream::trunc); + file.write(reinterpret_cast<char*>(mBuffer.data()), mBuffer.size()); + file.flush(); + file.close(); +} + +void DataStream::write(void* data, int sizeInBytes) +{ + if((int)mBuffer.size() < mWritePos + sizeInBytes) + mBuffer.resize(mWritePos + sizeInBytes); + memcpy(&mBuffer[mWritePos], data, sizeInBytes); + mWritePos += sizeInBytes; +}
\ No newline at end of file diff --git a/NvCloth/samples/SampleBase/utils/DataStream.h b/NvCloth/samples/SampleBase/utils/DataStream.h new file mode 100644 index 0000000..c47da39 --- /dev/null +++ b/NvCloth/samples/SampleBase/utils/DataStream.h @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, related documentation +* and any modifications thereto. Any use, reproduction, disclosure or +* distribution of this software and related documentation without an express +* license agreement from NVIDIA CORPORATION is strictly prohibited. +*/ + +#pragma once +#include <vector> +#include <string> +#include <assert.h> + +class DataStream +{ +public: + DataStream(); + DataStream(void* data, int sizeInBytes); + DataStream(const char* filename); + static bool fileExists(const char* filename); + + void saveToFile(const char* filename); + + template<typename T> + void write(T& value) + { + if(mBuffer.size() < mWritePos + sizeof(T)) + mBuffer.resize(mWritePos + sizeof(T)); + memcpy(&mBuffer[mWritePos], &value, sizeof(T)); + mWritePos += sizeof(T); + } + template<> + void write<std::string>(std::string& value) + { + int len = (int)value.length(); + if(mBuffer.size() < mWritePos + len + sizeof(int)) + mBuffer.resize(mWritePos + len + sizeof(int)); + memcpy(&mBuffer[mWritePos], &len, sizeof(int)); + mWritePos += sizeof(int); + memcpy(&mBuffer[mWritePos], value.c_str(), len); + mWritePos += len; + } + + void write(void* data, int sizeInBytes); + + template<typename T> + T read() + { + T value; + assert(mReadPos + sizeof(T) <= mBuffer.size()); + memcpy(&value, &mBuffer[mReadPos], sizeof(T)); + mReadPos += sizeof(T); + return value; + } + template<> + std::string read<std::string>() + { + int len = read<int>(); + std::string value; + value.resize(len); + assert(mReadPos + len < (int)mBuffer.size()); + memcpy(&value[0], &mBuffer[mReadPos], len); + mReadPos += len; + return value; + } +private: + std::vector<unsigned char> mBuffer; + int mReadPos; + int mWritePos; +};
\ No newline at end of file diff --git a/NvCloth/samples/SampleBase/utils/JobManager.cpp b/NvCloth/samples/SampleBase/utils/JobManager.cpp index 2a18d1c..a1e8454 100644 --- a/NvCloth/samples/SampleBase/utils/JobManager.cpp +++ b/NvCloth/samples/SampleBase/utils/JobManager.cpp @@ -41,9 +41,10 @@ void Job::Execute() else ExecuteInternal(); - mFinishedLock.lock(); - mFinished = true; - mFinishedLock.unlock(); + { + std::lock_guard<std::mutex> lock(mFinishedLock); + mFinished = true; + } mFinishedEvent.notify_one(); } @@ -53,16 +54,19 @@ void Job::AddReference() } void Job::RemoveReference() { - if (0 == --mRefCount) + int refCount = --mRefCount; + if (0 == refCount) { mParent->Submit(this); } + assert(refCount >= 0); } void Job::Wait() { std::unique_lock<std::mutex> lock(mFinishedLock); mFinishedEvent.wait(lock, [this](){return mFinished;} ); + lock.unlock(); return; } @@ -115,9 +119,12 @@ void MultithreadedSolverHelper::StartSimulation(float dt) if (mSolver->getSimulationChunkCount() != mSimulationChunkJobs.size()) { - mSimulationChunkJobs.resize(mSolver->getSimulationChunkCount(), Job()); + mSimulationChunkJobs.resize(mSolver->getSimulationChunkCount(), JobDependency()); for (int j = 0; j < mSolver->getSimulationChunkCount(); j++) - mSimulationChunkJobs[j].Initialize(mJobManager, [this, j](Job*) {mSolver->simulateChunk(j); mEndSimulationJob.RemoveReference(); }); + { + mSimulationChunkJobs[j].Initialize(mJobManager, [this, j](Job*) {mSolver->simulateChunk(j); }); + mSimulationChunkJobs[j].SetDependentJob(&mEndSimulationJob); + } } else { diff --git a/NvCloth/samples/SampleBase/utils/JobManager.h b/NvCloth/samples/SampleBase/utils/JobManager.h index 648fa33..7bb88c9 100644 --- a/NvCloth/samples/SampleBase/utils/JobManager.h +++ b/NvCloth/samples/SampleBase/utils/JobManager.h @@ -23,8 +23,8 @@ #include <queue> #include <atomic> -#include <task/PxTaskManager.h> -#include <task/PxTask.h> +#include "task/PxTaskManager.h" +#include "task/PxTask.h" namespace nv { @@ -78,9 +78,10 @@ class Job public: Job() = default; Job(const Job&); + ~Job() { mValid = false; } void Initialize(JobManager* parent, std::function<void(Job*)> function = std::function<void(Job*)>(), int refcount = 1); void Reset(int refcount = 1); //Call this before reusing a job that doesn't need to be reinitialized - void Execute(); + virtual void Execute(); void AddReference(); void RemoveReference(); void Wait(); //Block until job is finished @@ -94,6 +95,22 @@ private: bool mFinished; std::mutex mFinishedLock; std::condition_variable mFinishedEvent; + bool mValid = true; +}; + +//this Job is a dependency to another job +class JobDependency : public Job +{ +public: + void SetDependentJob(Job* job) { mDependendJob = job; } + virtual void Execute() override + { + auto dependendJob = mDependendJob; + Job::Execute(); + dependendJob->RemoveReference(); + } +private: + Job* mDependendJob; }; class JobManager @@ -134,10 +151,11 @@ public: function(i);*/ Job finalJob; finalJob.Initialize(this, std::function<void(Job*)>(), count); - Job jobs[count]; + JobDependency jobs[count]; for(int j = 0; j < count; j++) { - jobs[j].Initialize(this, [j, &finalJob, function](Job*) {function(j); finalJob.RemoveReference(); }); + jobs[j].Initialize(this, [j, &finalJob, function](Job*) {function(j); }); + jobs[j].SetDependentJob(&finalJob); jobs[j].RemoveReference(); } finalJob.Wait(); @@ -166,7 +184,7 @@ public: private: Job mStartSimulationJob; Job mEndSimulationJob; - std::vector<Job> mSimulationChunkJobs; + std::vector<JobDependency> mSimulationChunkJobs; float mDt; diff --git a/NvCloth/samples/SampleBase/utils/MeshGenerator.cpp b/NvCloth/samples/SampleBase/utils/MeshGenerator.cpp index 5541cc3..ccb3bf3 100644 --- a/NvCloth/samples/SampleBase/utils/MeshGenerator.cpp +++ b/NvCloth/samples/SampleBase/utils/MeshGenerator.cpp @@ -387,56 +387,28 @@ Mesh generateCone(physx::PxVec4 a, physx::PxVec4 b, int segments, float grow, bo //sphere a with smaller radius float cRadius = aRadius - bRadius; - physx::PxVec3 cCenter = aCenter; - - //sphere in between the a and b - physx::PxVec3 dCenter = (aCenter+bCenter)*0.5f; - float dRadius = (aCenter - bCenter).magnitude()*0.5f; - - //intersection between c and d to get tangent point - float iRadius; - physx::PxVec3 iCenter = IntersectSpheres(&iRadius, dCenter, dRadius, cCenter, cRadius); - physx::PxVec3 iPoint = iCenter + basis[0] * iRadius; //tangent point on c - physx::PxVec3 offset = (iPoint - aCenter).getNormalized(); //offset direction - - physx::PxVec3 aPoint = aCenter + offset*aRadius; - aCenter = (aPoint - aCenter).dot(basis[2])*basis[2] + aCenter; - aRadius = (aPoint - aCenter).magnitude(); - physx::PxVec3 bPoint = bCenter + offset*bRadius; - bCenter = (bPoint - aCenter).dot(basis[2])*basis[2] + aCenter; - bRadius = (bPoint - bCenter).magnitude(); - - - } - - //old code, probably wrong - /* - physx::PxVec3 pa = aCenter + aRadius*basis[0]; - physx::PxVec3 pb = bCenter + bRadius*basis[0]; - physx::PxVec3 dir = pb - pa; - - //construct plane containing pa and pb, with normal perpendicular to basis[0] - physx::PxVec3 n = basis[2].cross(dir); - physx::PxVec3 n2 = dir.cross(n); - - //line plane intersection - physx::PxVec3 focusPoint = aCenter + ((pa - aCenter).dot(n2)) / basis[2].dot(n2) * basis[2]; - - { - float focusDistance = (focusPoint - aCenter).magnitude(); - //make circle with center in mid point between focusPoint and aCenter - physx::PxVec3 cCenter = (focusPoint + aCenter)*0.5f; - float cRadius = focusDistance*0.5f; - - aCenter = IntersectSpheres(&aRadius, aCenter, aRadius, cCenter, cRadius); + if(cRadius > 0.00001) + { + physx::PxVec3 cCenter = aCenter; + + //sphere in between the a and b + physx::PxVec3 dCenter = (aCenter + bCenter)*0.5f; + float dRadius = (aCenter - bCenter).magnitude()*0.5f; + + //intersection between c and d to get tangent point + float iRadius; + physx::PxVec3 iCenter = IntersectSpheres(&iRadius, dCenter, dRadius, cCenter, cRadius); + physx::PxVec3 iPoint = iCenter + basis[0] * iRadius; //tangent point on c + physx::PxVec3 offset = (iPoint - aCenter).getNormalized(); //offset direction + + physx::PxVec3 aPoint = aCenter + offset*aRadius; + aCenter = (aPoint - aCenter).dot(basis[2])*basis[2] + aCenter; + aRadius = (aPoint - aCenter).magnitude(); + physx::PxVec3 bPoint = bCenter + offset*bRadius; + bCenter = (bPoint - aCenter).dot(basis[2])*basis[2] + aCenter; + bRadius = (bPoint - bCenter).magnitude(); + } } - - { - float focusDistance = (focusPoint - bCenter).magnitude(); - physx::PxVec3 cCenter = (focusPoint + bCenter)*0.5f; - float cRadius = focusDistance*0.5f; - bCenter = IntersectSpheres(&bRadius, bCenter, bRadius, cCenter, cRadius); - }*/ } @@ -491,7 +463,7 @@ Mesh generateCollisionCapsules(physx::PxVec4* spheres, int sphereCount, uint32_t Mesh finalMesh; for(int i = 0; i < sphereCount; i++) { - Mesh sphere = generateIcosahedron(spheres[i].w+ grow, 4); + Mesh sphere = generateIcosahedron(spheres[i].w+ grow, 2); sphere.applyTransfom(physx::PxTransform(spheres[i].getXYZ())); finalMesh.merge(sphere); } @@ -504,6 +476,272 @@ Mesh generateCollisionCapsules(physx::PxVec4* spheres, int sphereCount, uint32_t return finalMesh; } +::SimpleMesh generateFastSphere(int segmentsX, int segmentY, physx::PxMat44 transform) +{ + SimpleMesh mesh; + const int xSegments = segmentsX; + const int ySegments = segmentY; + + { + //bottom + SimpleMesh::Vertex v; + v.position = physx::PxVec3(0.0f, -1.0f, 0.0f); + v.normal = transform.rotate(physx::PxVec4(v.position, 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + v.uv = physx::PxVec2(0.0f, 0.0f); + mesh.vertices.push_back(v); + } + + //middle + for(int y = 1; y < ySegments; y++) + { + for(int x = 0; x < xSegments; x++) + { + float xf = (float)x / (xSegments - 1.0f); + float yaw = xf*physx::PxTwoPi; + float yf = (float)y / (ySegments); + float pitch = (yf - 0.5f)*physx::PxPi; + + SimpleMesh::Vertex v; + v.position = physx::PxVec3(cos(yaw)*cos(pitch), sin(pitch), sin(yaw)*cos(pitch)); + v.uv = physx::PxVec2(xf, yf); + v.normal = transform.rotate(physx::PxVec4(v.position, 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + mesh.vertices.push_back(v); + } + } + + { + //top + SimpleMesh::Vertex v; + v.position = physx::PxVec3(0.0f, 1.0f, 0.0f); + v.normal = transform.rotate(physx::PxVec4(v.position,0.0f)).getXYZ(); + v.position = transform.transform(v.position); + v.uv = physx::PxVec2(0.0f, 1.0f); + mesh.vertices.push_back(v); + } + + //bottom cap + for(int x = 0; x < xSegments; x++) + { + mesh.indices.push_back(0); + mesh.indices.push_back(1 + x); + mesh.indices.push_back(1 + (x + 1) % xSegments); + } + + const auto RingVertex = [xSegments, ySegments](int x, int y) + { + return 1 + y*xSegments + x%xSegments; + }; + + //middle + for(int y = 0; y < ySegments - 2; y++) + { + for(int x = 0; x < xSegments; x++) + { + mesh.indices.push_back(RingVertex(x, y)); + mesh.indices.push_back(RingVertex(x + 1, y)); + mesh.indices.push_back(RingVertex(x, y + 1)); + + mesh.indices.push_back(RingVertex(x + 1, y)); + mesh.indices.push_back(RingVertex(x + 1, y + 1)); + mesh.indices.push_back(RingVertex(x, y + 1)); + } + } + + //bottom cap + for(int x = 0; x < xSegments; x++) + { + mesh.indices.push_back((uint16_t)mesh.vertices.size() - 1); + mesh.indices.push_back(RingVertex(x, ySegments - 2)); + mesh.indices.push_back(RingVertex(x + 1, ySegments - 2)); + } + + return mesh; +} + +::SimpleMesh generateFastCylinder(int segmentsX, int segmentY, physx::PxMat44 transform) +{ + SimpleMesh mesh; + const int xSegments = segmentsX; + const int ySegments = segmentY; + + + //middle + for(int y = 0; y < ySegments+1; y++) + { + for(int x = 0; x < xSegments; x++) + { + float xf = (float)x / (xSegments - 1.0f); + float yaw = xf*physx::PxTwoPi; + float yf = (float)y / (ySegments) * 2.0f - 1.0f; + + SimpleMesh::Vertex v; + v.position = physx::PxVec3(cos(yaw), yf, sin(yaw)); + v.uv = physx::PxVec2(xf, yf); + v.normal = transform.rotate(physx::PxVec4(physx::PxVec3(cos(yaw), 0.0f, sin(yaw)), 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + mesh.vertices.push_back(v); + } + } + + + const auto RingVertex = [xSegments, ySegments](int x, int y) + { + return y*xSegments + x%xSegments; + }; + + //middle + for(int y = 0; y < ySegments; y++) + { + for(int x = 0; x < xSegments; x++) + { + mesh.indices.push_back(RingVertex(x, y)); + mesh.indices.push_back(RingVertex(x + 1, y)); + mesh.indices.push_back(RingVertex(x, y + 1)); + + mesh.indices.push_back(RingVertex(x + 1, y)); + mesh.indices.push_back(RingVertex(x + 1, y + 1)); + mesh.indices.push_back(RingVertex(x, y + 1)); + } + } + + return mesh; +} + +::SimpleMesh generateCollisionCapsulesFast(physx::PxVec4* spheres, int sphereCount, uint32_t* indices, int indexCount, float grow) +{ + static ::SimpleMesh sphere = generateFastSphere(24,12,physx::PxTransform(physx::PxVec3(0.0f, 0.0f, 0.0f), physx::PxQuat(0.0f, physx::PxVec3(0.0f, 1.0f, 0.0f)))); + static ::SimpleMesh cylinder = generateFastCylinder(24, 1, physx::PxTransform(physx::PxVec3(0.0f, 1.0f, 0.0f), physx::PxQuat(0.0f, physx::PxVec3(0.0f, 1.0f, 0.0f)))); + + ::SimpleMesh mesh; + mesh.vertices.resize(sphere.vertices.size()*sphereCount + cylinder.vertices.size()*(indexCount / 2)); + mesh.indices.resize(sphere.indices.size()*sphereCount + cylinder.indices.size()*(indexCount / 2)); + + int nextVertex = 0; + int nextIndex = 0; + for(int i = 0; i < sphereCount; i++) + { + int baseIndex = nextVertex; + physx::PxMat44 transform = + physx::PxMat44(physx::PxMat33(physx::PxIdentity), spheres[i].getXYZ()) + * physx::PxMat44(PxVec4(spheres[i].w + grow, spheres[i].w + grow, spheres[i].w + grow, 1.0f)); + + for(int vi = 0; vi<(int)sphere.vertices.size(); vi++) + { + SimpleMesh::Vertex v = sphere.vertices[vi]; + v.normal = transform.rotate(physx::PxVec4(v.normal, 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + mesh.vertices[nextVertex++] = v; + } + + for(int ii = 0; ii < (int)sphere.indices.size(); ii++) + { + mesh.indices[nextIndex++] = sphere.indices[ii]+ baseIndex; + } + } + + for(int i = 0; i < indexCount; i+=2) + { + int baseIndex = nextVertex; + + physx::PxVec3 spherePosA = spheres[indices[i]].getXYZ(); + physx::PxVec3 spherePosB = spheres[indices[i+1]].getXYZ(); + float sphereRadiusA = spheres[indices[i]].w + grow; + float sphereRadiusB = spheres[indices[i + 1]].w + grow; + + if(sphereRadiusA < sphereRadiusB) + { + std::swap(sphereRadiusA, sphereRadiusB); + std::swap(spherePosA, spherePosB); + } + + { + //http://jwilson.coe.uga.edu/emt669/Student.Folders/Kertscher.Jeff/Essay.3/Tangents.html + + //sphere a with smaller radius + float cRadius = sphereRadiusA - sphereRadiusB; + if(cRadius > 0.00001) + { + physx::PxVec3 basis[3]; + basis[2] = spherePosB - spherePosA; + basis[2].normalize(); + computeBasis(basis[2], &basis[0], &basis[1]); + + physx::PxVec3 cCenter = spherePosA; + + //sphere in between the a and b + physx::PxVec3 dCenter = (spherePosA + spherePosB)*0.5f; + float dRadius = (spherePosA - spherePosB).magnitude()*0.5f; + + //intersection between c and d to get tangent point + float iRadius; + physx::PxVec3 iCenter = IntersectSpheres(&iRadius, dCenter, dRadius, cCenter, cRadius); + physx::PxVec3 iPoint = iCenter + basis[0] * iRadius; //tangent point on c + physx::PxVec3 offset = (iPoint - spherePosA).getNormalized(); //offset direction + + physx::PxVec3 aPoint = spherePosA + offset*sphereRadiusA; + spherePosA = (aPoint - spherePosA).dot(basis[2])*basis[2] + spherePosA; + sphereRadiusA = (aPoint - spherePosA).magnitude(); + physx::PxVec3 bPoint = spherePosB + offset*sphereRadiusB; + spherePosB = (bPoint - spherePosA).dot(basis[2])*basis[2] + spherePosA; + sphereRadiusB = (bPoint - spherePosB).magnitude(); + } + } + + float length = (spherePosB - spherePosA).magnitude(); + + + physx::PxMat44 scaleA = physx::PxMat44(PxVec4(sphereRadiusA, length/2.0f, sphereRadiusA+grow, 1.0f)); + physx::PxMat44 scaleB = physx::PxMat44(PxVec4(sphereRadiusB, length/2.0f, sphereRadiusB, 1.0f)); + + physx::PxQuat orientation; + { + physx::PxVec3 u = physx::PxVec3(0.0f, 1.0f, 0.0f); + physx::PxVec3 v = spherePosB - spherePosA; + v.normalize(); + + if(u.dot(v) < -0.9999) + orientation = physx::PxQuat(physx::PxTwoPi, physx::PxVec3(1.0f, 0.0f, 0.0f)); + else if(u.dot(v) > 0.9999) + orientation = physx::PxQuat(0.0f, physx::PxVec3(1.0f, 0.0f, 0.0f)); + else + { + physx::PxVec3 half = u + v; + half.normalize(); + physx::PxVec3 imaginary = u.cross(half); + orientation = physx::PxQuat(imaginary.x, imaginary.y, imaginary.z, u.dot(half)); + } + } + + physx::PxMat44 transform = physx::PxMat44(physx::PxTransform(spherePosA, orientation))*scaleA; + + int firstRing = (int)cylinder.vertices.size() / 2; + for(int vi = 0; vi<firstRing; vi++) + { + SimpleMesh::Vertex v = cylinder.vertices[vi]; + v.normal = transform.rotate(physx::PxVec4(v.normal, 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + mesh.vertices[nextVertex++] = v; + } + transform = physx::PxMat44(physx::PxTransform(spherePosA, orientation))*scaleB; + for(int vi = firstRing; vi<(int)cylinder.vertices.size(); vi++) + { + SimpleMesh::Vertex v = cylinder.vertices[vi]; + v.normal = transform.rotate(physx::PxVec4(v.normal, 0.0f)).getXYZ(); + v.position = transform.transform(v.position); + mesh.vertices[nextVertex++] = v; + } + + for(int ii = 0; ii < (int)cylinder.indices.size(); ii++) + { + mesh.indices[nextIndex++] = cylinder.indices[ii] + baseIndex; + } + } + + return mesh; +} + uint32_t generateConvexPolyhedronPlanes(int segmentsX, int segmentsY, physx::PxVec3 center, float radius, std::vector<physx::PxVec4>* planes) { int offset = 0; @@ -546,7 +784,7 @@ MeshGeneratorRenderMesh::MeshGeneratorRenderMesh(const Mesh mesh) layout.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}); layout.push_back({"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}); - initialize(vertices, (uint32_t)vertexCount, sizeof(RenderVertex), layout, indices, indexCount); + initialize(vertices, (uint32_t)vertexCount, sizeof(RenderVertex), layout, indices, indexCount, 0); delete vertices; delete indices; @@ -567,11 +805,22 @@ MeshGeneratorRenderMeshSmooth::MeshGeneratorRenderMeshSmooth(const Mesh mesh) layout.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}); layout.push_back({"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}); - initialize(vertices, (uint32_t)vertexCount, sizeof(RenderVertex), layout, indices, indexCount); + initialize(vertices, (uint32_t)vertexCount, sizeof(RenderVertex), layout, indices, indexCount, 0); delete vertices; delete indices; } + +MeshGeneratorRenderMeshSmooth::MeshGeneratorRenderMeshSmooth(const ::SimpleMesh mesh, int flags) +{ + std::vector<D3D11_INPUT_ELEMENT_DESC> layout; + layout.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}); + layout.push_back({"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}); + layout.push_back({"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0}); + + initialize(mesh.vertices.data(), (uint32_t)mesh.vertices.size(), sizeof(::SimpleMesh::Vertex), layout, mesh.indices.data(), (uint32_t)mesh.indices.size(), flags); +} + MeshGeneratorRenderMeshSmooth::~MeshGeneratorRenderMeshSmooth() { diff --git a/NvCloth/samples/SampleBase/utils/MeshGenerator.h b/NvCloth/samples/SampleBase/utils/MeshGenerator.h index 4f4b0c9..2e0344e 100644 --- a/NvCloth/samples/SampleBase/utils/MeshGenerator.h +++ b/NvCloth/samples/SampleBase/utils/MeshGenerator.h @@ -14,6 +14,7 @@ #include <vector> #include "renderer/CustomRenderMesh.h" #include <foundation/PxVec3.h> +#include "renderer/Mesh.h" namespace MeshGenerator { @@ -92,6 +93,13 @@ Mesh generateCone(physx::PxVec4 a, physx::PxVec4 b, int segments, float grow, bo Mesh generateCollisionConvex(physx::PxVec4* planes, uint32_t mask, float grow, bool flip); Mesh generateCollisionCapsules(physx::PxVec4* spheres, int sphereCount, uint32_t* indices, int indexCount, float grow); +//Generates simple meshes with smooth shading +::SimpleMesh generateFastSphere(int segmentsX, int segmentY, physx::PxMat44 transform); +::SimpleMesh generateFastCylinder(int segmentsX, int segmentY, physx::PxMat44 transform); //no caps + +//Combines cashed spheres and cylinders to generate the capsules +::SimpleMesh generateCollisionCapsulesFast(physx::PxVec4* spheres, int sphereCount, uint32_t* indices, int indexCount, float grow); + uint32_t generateConvexPolyhedronPlanes(int segmentsX, int segmentsY, physx::PxVec3 center, float radius, std::vector<physx::PxVec4>* planes); class MeshGeneratorRenderMesh : public CustomRenderMesh @@ -105,6 +113,7 @@ class MeshGeneratorRenderMeshSmooth : public CustomRenderMesh { public: MeshGeneratorRenderMeshSmooth(const Mesh mesh); + MeshGeneratorRenderMeshSmooth(const ::SimpleMesh mesh, int flags = 0); //flags from CustomRenderMesh virtual ~MeshGeneratorRenderMeshSmooth(); }; |