// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2019.08.13 #pragma once #include #include #include #include namespace WwiseGTE { template class TubeMesh : public Mesh { public: // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified // medial curve and radial function. The mesh has torus topology // when 'closed' is true and has cylinder topology when 'closed' is // false. The client is responsible for setting the topology // correctly in the 'description' input. The rows correspond to // medial samples and the columns correspond to radial samples. The // medial curve is sampled according to its natural t-parameter when // 'sampleByArcLength' is false; otherwise, it is sampled uniformly // in arclength. TODO: Allow TORUS and remove the 'closed' input TubeMesh(MeshDescription const& description, std::shared_ptr> const& medial, std::function const& radial, bool closed, bool sampleByArcLength, Vector3 upVector) : Mesh(description, { MeshTopology::CYLINDER }), mMedial(medial), mRadial(radial), mClosed(closed), mSampleByArcLength(sampleByArcLength), mUpVector(upVector) { if (!this->mDescription.constructed) { // The logger system will report these errors in the Mesh // constructor. mMedial = nullptr; return; } LogAssert(mMedial != nullptr, "A nonnull medial curve is required."); mCosAngle.resize(this->mDescription.numCols); mSinAngle.resize(this->mDescription.numCols); Real invRadialSamples = (Real)1 / (Real)(this->mDescription.numCols - 1); for (unsigned int i = 0; i < this->mDescription.numCols - 1; ++i) { Real angle = i * invRadialSamples * (Real)GTE_C_TWO_PI; mCosAngle[i] = std::cos(angle); mSinAngle[i] = std::sin(angle); } mCosAngle[this->mDescription.numCols - 1] = mCosAngle[0]; mSinAngle[this->mDescription.numCols - 1] = mSinAngle[0]; Real invDenom; if (mClosed) { invDenom = (Real)1 / (Real)this->mDescription.numRows; } else { invDenom = (Real)1 / (Real)(this->mDescription.numRows - 1); } Real factor; if (mSampleByArcLength) { factor = mMedial->GetTotalLength() * invDenom; mTSampler = [this, factor](unsigned int row) { return mMedial->GetTime(row * factor); }; } else { factor = (mMedial->GetTMax() - mMedial->GetTMin()) * invDenom; mTSampler = [this, factor](unsigned int row) { return mMedial->GetTMin() + row * factor; }; } if (mUpVector != Vector3::Zero()) { mFSampler = [this](Real t) { std::array, 4> frame; frame[0] = mMedial->GetPosition(t); frame[1] = mMedial->GetTangent(t); frame[3] = UnitCross(frame[1], mUpVector); frame[2] = UnitCross(frame[3], frame[1]); return frame; }; } else { mFrenet = std::make_unique>(mMedial); mFSampler = [this](Real t) { std::array, 4> frame; (*mFrenet)(t, frame[0], frame[1], frame[2], frame[3]); return frame; }; } if (!this->mTCoords) { mDefaultTCoords.resize(this->mDescription.numVertices); this->mTCoords = mDefaultTCoords.data(); this->mTCoordStride = sizeof(Vector2); this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; if (this->mDescription.allowUpdateFrame) { if (!this->mDescription.hasTangentSpaceVectors) { this->mDescription.allowUpdateFrame = false; } if (!this->mNormals) { this->mDescription.allowUpdateFrame = false; } } } this->ComputeIndices(); InitializeTCoords(); UpdatePositions(); if (this->mDescription.allowUpdateFrame) { this->UpdateFrame(); } else if (this->mNormals) { this->UpdateNormals(); } } // Member access. inline std::shared_ptr> const& GetMedial() const { return mMedial; } inline std::function const& GetRadial() const { return mRadial; } inline bool IsClosed() const { return mClosed; } inline bool IsSampleByArcLength() const { return mSampleByArcLength; } inline Vector3 const& GetUpVector() const { return mUpVector; } private: void InitializeTCoords() { Vector2tcoord; for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) { tcoord[1] = (Real)r / (Real)this->mDescription.rMax; for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) { tcoord[0] = (Real)c / (Real)this->mDescription.numCols; this->TCoord(i) = tcoord; } } } virtual void UpdatePositions() override { uint32_t row, col, v, save; for (row = 0, v = 0; row < this->mDescription.numRows; ++row, ++v) { Real t = mTSampler(row); Real radius = mRadial(t); // frame = (position, tangent, normal, binormal) std::array, 4> frame = mFSampler(t); for (col = 0, save = v; col < this->mDescription.numCols; ++col, ++v) { this->Position(v) = frame[0] + radius * (mCosAngle[col] * frame[2] + mSinAngle[col] * frame[3]); } this->Position(v) = this->Position(save); } if (mClosed) { for (col = 0; col < this->mDescription.numCols; ++col) { uint32_t i0 = col; uint32_t i1 = col + this->mDescription.numCols * (this->mDescription.numRows - 1); this->Position(i1) = this->Position(i0); } } } std::shared_ptr> mMedial; std::function mRadial; bool mClosed, mSampleByArcLength; Vector3 mUpVector; std::vector mCosAngle, mSinAngle; std::function mTSampler; std::function, 4>(Real)> mFSampler; std::unique_ptr> mFrenet; // If the client does not request texture coordinates, they will be // computed internally for use in evaluation of the surface geometry. std::vector> mDefaultTCoords; }; }