ENH: improve gltf handling

- scene

  - write with fileName, additional getMesh accessor

  - addColourToMesh accepts an alpha field size 1 as a constant
    alpha value

  - sceneWriter wrapper

ENH: improve gltf handling of colour and alpha specification

- accept plain input directly.
  Eg,
      colour  (1 0 1);
  vs
      colour      uniform;
      colourValue (1 0 1);

- use field magnitude for colouring of non-scalar fields.

  Eg, having three different colour maps for a vector field simply
  does not help much with visualisation.
This commit is contained in:
Mark Olesen
2022-01-27 18:07:06 +01:00
parent 06ade9515e
commit 2a61606251
11 changed files with 685 additions and 270 deletions

View File

@ -36,6 +36,7 @@ gltf/foamGltfBufferView.C
gltf/foamGltfMesh.C gltf/foamGltfMesh.C
gltf/foamGltfObject.C gltf/foamGltfObject.C
gltf/foamGltfScene.C gltf/foamGltfScene.C
gltf/foamGltfSceneWriter.C
starcd/STARCDCore.C starcd/STARCDCore.C
stl/STLCore.C stl/STLCore.C

View File

@ -178,7 +178,7 @@ public:
//- Predefined tables //- Predefined tables
static const HashPtrTable<colourTable>& tables(); static const HashPtrTable<colourTable>& tables();
//- Return the colour at x (within 0-1 range) //- Return the colour at x. The input is clipped to 0-1 range.
vector value(const scalar x) const; vector value(const scalar x) const;
//- Return a discrete lookup table of colours //- Return a discrete lookup table of colours

View File

@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Description
Forward declarations for exposed glTF interfaces
\*---------------------------------------------------------------------------*/
#ifndef Foam_gltf_Forward_Declarations_H
#define Foam_gltf_Forward_Declarations_H
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
namespace glTF
{
// Forward Declarations
class scene;
class sceneWriter;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace glTF
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -28,15 +28,14 @@ License
template<class Type> template<class Type>
void Foam::glTF::object::addData(const Type& fld) void Foam::glTF::object::addData(const Type& fld)
{ {
const label nComponents = const direction nCmpts = pTraits<typename Type::value_type>::nComponents;
pTraits<typename Type::value_type>::nComponents;
label count = data_.size(); label count = data_.size();
data_.setSize(data_.size() + fld.size()*nComponents); data_.resize(data_.size() + fld.size()*nCmpts);
forAll(fld, fieldi) forAll(fld, fieldi)
{ {
for (direction d = 0; d < nComponents; ++d) for (direction d = 0; d < nCmpts; ++d)
{ {
data_[count++] = component(fld[fieldi], d); data_[count++] = component(fld[fieldi], d);
} }
@ -45,7 +44,7 @@ void Foam::glTF::object::addData(const Type& fld)
template<class Type1, class Type2> template<class Type1, class Type2>
void Foam::glTF::object::addData(const Type1& fld1, const Type2&fld2) void Foam::glTF::object::addData(const Type1& fld1, const Type2& fld2)
{ {
if (fld1.size() != fld2.size()) if (fld1.size() != fld2.size())
{ {
@ -55,26 +54,20 @@ void Foam::glTF::object::addData(const Type1& fld1, const Type2&fld2)
<< abort(FatalError); << abort(FatalError);
} }
const label nComponents1 = const direction nCmpts1 = pTraits<typename Type1::value_type>::nComponents;
pTraits<typename Type1::value_type>::nComponents; const direction nCmpts2 = pTraits<typename Type2::value_type>::nComponents;
const label nComponents2 =
pTraits<typename Type2::value_type>::nComponents;
label count = data_.size(); label count = data_.size();
data_.setSize data_.resize(data_.size() + fld1.size()*(nCmpts1 + nCmpts2));
(
data_.size() + fld1.size()*(nComponents1 + nComponents2)
);
forAll(fld1, fieldi) forAll(fld1, fieldi)
{ {
for (direction d = 0; d < nComponents1; ++d) for (direction d = 0; d < nCmpts1; ++d)
{ {
data_[count++] = component(fld1[fieldi], d); data_[count++] = component(fld1[fieldi], d);
} }
for (direction d = 0; d < nComponents2; ++d) for (direction d = 0; d < nCmpts2; ++d)
{ {
data_[count++] = component(fld2[fieldi], d); data_[count++] = component(fld2[fieldi], d);
} }

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021 OpenCFD Ltd. Copyright (C) 2021-2022 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -26,7 +26,8 @@ License
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
#include "foamGltfScene.H" #include "foamGltfScene.H"
#include "fileName.H" #include "OFstream.H"
#include "OSspecific.H"
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
@ -43,6 +44,26 @@ Foam::glTF::scene::scene()
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
Foam::glTF::mesh& Foam::glTF::scene::getMesh(label meshi)
{
const label lastMeshi = (meshes_.size() - 1);
if (meshi < 0)
{
meshi = (lastMeshi < 0 ? static_cast<label>(0) : lastMeshi);
}
if (meshi > lastMeshi)
{
FatalErrorInFunction
<< "Mesh " << meshi << " out of range: " << lastMeshi
<< abort(FatalError);
}
return meshes_[meshi];
}
Foam::label Foam::glTF::scene::addColourToMesh Foam::label Foam::glTF::scene::addColourToMesh
( (
const vectorField& fld, const vectorField& fld,
@ -51,13 +72,7 @@ Foam::label Foam::glTF::scene::addColourToMesh
const scalarField& alpha const scalarField& alpha
) )
{ {
if (meshi > meshes_.size() - 1) auto& gmesh = getMesh(meshi);
{
FatalErrorInFunction
<< "Mesh " << meshi << " out of range "
<< (meshes_.size() - 1)
<< abort(FatalError);
}
auto& bv = bufferViews_.create(name); auto& bv = bufferViews_.create(name);
bv.byteOffset() = bytes_; bv.byteOffset() = bytes_;
@ -71,21 +86,29 @@ Foam::label Foam::glTF::scene::addColourToMesh
auto& obj = objects_.create(name); auto& obj = objects_.create(name);
if (alpha.size()) if (alpha.empty())
{
obj.addData(fld);
}
else
{ {
bv.byteLength() += fld.size()*sizeof(float); bv.byteLength() += fld.size()*sizeof(float);
bytes_ += fld.size()*sizeof(float); bytes_ += fld.size()*sizeof(float);
acc.type() = "VEC4"; acc.type() = "VEC4";
obj.addData(fld, alpha); // Support uniform alpha vs full alpha field
} tmp<scalarField> talpha(alpha);
else
if (alpha.size() == 1 && alpha.size() < fld.size())
{ {
obj.addData(fld); talpha = tmp<scalarField>::New(fld.size(), alpha[0]);
} }
meshes_[meshi].addColour(acc.id()); obj.addData(fld, talpha());
}
gmesh.addColour(acc.id());
return acc.id(); return acc.id();
} }
@ -136,13 +159,27 @@ void Foam::glTF::scene::addToAnimation
} }
void Foam::glTF::scene::write(const fileName& outputFile)
{
fileName jsonFile(outputFile.lessExt());
jsonFile.ext("gltf");
// Note: called on master only
if (!isDir(jsonFile.path()))
{
mkDir(jsonFile.path());
}
OFstream os(jsonFile);
write(os);
}
void Foam::glTF::scene::write(Ostream& os) void Foam::glTF::scene::write(Ostream& os)
{ {
const fileName base(os.name().lessExt()); fileName binFile(os.name().lessExt());
const fileName binFile binFile.ext("bin");
(
fileName::concat(base.path(), fileName::name(base) + ".bin")
);
// Write binary file // Write binary file
// Note: using stdStream // Note: using stdStream

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021 OpenCFD Ltd. Copyright (C) 2021-2022 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -38,9 +38,10 @@ SourceFiles
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
#ifndef foam_gltf_scene_H #ifndef Foam_gltf_scene_H
#define foam_gltf_scene_H #define Foam_gltf_scene_H
#include "fileName.H"
#include "foamGltfList.H" #include "foamGltfList.H"
#include "foamGltfObject.H" #include "foamGltfObject.H"
#include "foamGltfMesh.H" #include "foamGltfMesh.H"
@ -49,12 +50,15 @@ SourceFiles
#include "foamGltfAnimation.H" #include "foamGltfAnimation.H"
#include "scalarField.H" #include "scalarField.H"
#include "vectorField.H" #include "vectorField.H"
#include "OFstream.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam namespace Foam
{ {
// Forward Declarations
class OFstream;
namespace glTF namespace glTF
{ {
@ -85,6 +89,13 @@ class scene
label bytes_; label bytes_;
// Private Member Functions
//- Non-const access to mesh at index (can be -1 for last mesh)
// FatalError for out-of-bounds
mesh& getMesh(label meshi);
public: public:
// Constructors // Constructors
@ -114,16 +125,19 @@ public:
( (
const Type& fld, const Type& fld,
const word& name, const word& name,
const label meshi const label meshId
); );
//- Returns accessor index //- Add a colour field to the mesh, optionally with an alpha channel.
// A constant alpha value can be specified as a field of size 1.
//
// \returns accessor index
label addColourToMesh label addColourToMesh
( (
const vectorField& fld, const vectorField& fld, //!< RGB colour field
const word& name, const word& name,
const label meshi, const label meshId,
const scalarField& alpha = scalarField() const scalarField& alpha = scalarField::null() //!< Alpha channel
); );
//- Returns index of last animation //- Returns index of last animation
@ -139,7 +153,13 @@ public:
const string& interpolation = "LINEAR" const string& interpolation = "LINEAR"
); );
//- Write to stream (JSON and binary data)
// Write
//- Write to file pair (.gltf, .bin)
void write(const fileName& outputFile);
//- Write JSON (.gltf) to stream with auxiliary binary data (.bin)
void write(Ostream& os); void write(Ostream& os);
}; };

View File

@ -33,11 +33,11 @@ Foam::label Foam::glTF::scene::addField
const label target const label target
) )
{ {
const label nComponents = pTraits<typename Type::value_type>::nComponents; const direction nCmpts = pTraits<typename Type::value_type>::nComponents;
auto& bv = bufferViews_.create(name); auto& bv = bufferViews_.create(name);
bv.byteOffset() = bytes_; bv.byteOffset() = bytes_;
bv.byteLength() = fld.size()*nComponents*sizeof(float); bv.byteLength() = fld.size()*nCmpts*sizeof(float);
if (target != -1) if (target != -1)
{ {
bv.target() = target; bv.target() = target;
@ -61,8 +61,8 @@ Foam::label Foam::glTF::scene::addMesh(const Type& fld, const word& name)
const label accessorId = const label accessorId =
addField(fld, name, key(targetTypes::ARRAY_BUFFER)); addField(fld, name, key(targetTypes::ARRAY_BUFFER));
auto& mesh = meshes_.create(name); auto& gmesh = meshes_.create(name);
mesh.accessorId() = accessorId; gmesh.accessorId() = accessorId;
return meshes_.size() - 1; return meshes_.size() - 1;
} }
@ -76,17 +76,11 @@ Foam::label Foam::glTF::scene::addFieldToMesh
const label meshi const label meshi
) )
{ {
if (meshi > meshes_.size() - 1) auto& gmesh = getMesh(meshi);
{
FatalErrorInFunction
<< "Mesh " << meshi << " out of range "
<< (meshes_.size() - 1)
<< abort(FatalError);
}
const label accessorId = addField(fld, name); const label accessorId = addField(fld, name);
meshes_[meshi].addField(name, accessorId); gmesh.addField(name, accessorId);
return accessorId; return accessorId;
} }

View File

@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "foamGltfSceneWriter.H"
#include "OFstream.H"
#include "OSspecific.H"
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::glTF::sceneWriter::sceneWriter(const fileName& outputFile)
:
ofile_(nullptr),
scene_(nullptr)
{
open(outputFile);
}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::glTF::sceneWriter::~sceneWriter()
{
close();
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::glTF::sceneWriter::valid() const noexcept
{
return (ofile_ && scene_);
}
const Foam::fileName& Foam::glTF::sceneWriter::path() const
{
return (ofile_ ? ofile_->name() : fileName::null);
}
const Foam::glTF::scene& Foam::glTF::sceneWriter::getScene() const
{
return *scene_;
}
Foam::glTF::scene& Foam::glTF::sceneWriter::getScene()
{
return *scene_;
}
void Foam::glTF::sceneWriter::open(const fileName& outputFile)
{
close();
fileName jsonFile(outputFile.lessExt());
jsonFile.ext("gltf");
// Note: called on master only
if (!isDir(jsonFile.path()))
{
mkDir(jsonFile.path());
}
ofile_.reset(new OFstream(jsonFile));
scene_.reset(new glTF::scene());
}
void Foam::glTF::sceneWriter::close()
{
if (ofile_ && scene_)
{
scene_->write(*ofile_);
}
ofile_.reset(nullptr);
scene_.reset(nullptr);
}
// ************************************************************************* //

View File

@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Class
Foam::glTF::sceneWriter
Description
Wrapper for glTF scene for file output
SourceFiles
foamGltfSceneWriter.C
\*---------------------------------------------------------------------------*/
#ifndef Foam_gltf_sceneWriter_H
#define Foam_gltf_sceneWriter_H
#include "autoPtr.H"
#include "fileName.H"
#include "foamGltfScene.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
// Forward Declarations
class OFstream;
namespace glTF
{
/*---------------------------------------------------------------------------*\
Class glTF::sceneWriter Declaration
\*---------------------------------------------------------------------------*/
class sceneWriter
{
// Private Data
//- The backend output stream (json)
autoPtr<OFstream> ofile_;
//- The scene to output
autoPtr<glTF::scene> scene_;
public:
// Generated Methods
//- No copy construct
sceneWriter(const sceneWriter&) = delete;
//- No copy assignment
void operator=(const sceneWriter&) = delete;
// Constructors
//- Default construct
sceneWriter() = default;
//- Construct and open with given file name
explicit sceneWriter(const fileName& outputFile);
//- Destructor - calls close()
~sceneWriter();
// Member Functions
//- True if output file and scene exist
bool valid() const noexcept;
//- The json file name. Empty with !valid()
const fileName& path() const;
//- Const access to the scene. Error if valid() is not true!
const scene& getScene() const;
//- Non-const access to the scene. Error if valid() is not true!
scene& getScene();
//- Flush, output and open a new file for output
void open(const fileName& outputFile);
//- Write scene and close file
void close();
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace glTF
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021 OpenCFD Ltd. Copyright (C) 2021-2022 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -39,6 +39,7 @@ template<class Type>
const Foam::Enum<typename Foam::gltfSetWriter<Type>::fieldOption> const Foam::Enum<typename Foam::gltfSetWriter<Type>::fieldOption>
Foam::gltfSetWriter<Type>::fieldOptionNames_ Foam::gltfSetWriter<Type>::fieldOptionNames_
({ ({
// No naming for NONE
{ fieldOption::UNIFORM, "uniform" }, { fieldOption::UNIFORM, "uniform" },
{ fieldOption::FIELD, "field" }, { fieldOption::FIELD, "field" },
}); });
@ -70,26 +71,19 @@ const Foam::colourTable& Foam::gltfSetWriter<Type>::getColourTable
template<class Type> template<class Type>
Foam::scalar Foam::gltfSetWriter<Type>::getFieldMin Foam::scalarMinMax Foam::gltfSetWriter<Type>::getFieldLimits
( (
const word& fieldName const word& fieldName
) const ) const
{ {
const dictionary fieldDict = fieldInfoDict_.subOrEmptyDict(fieldName); const dictionary fieldDict = fieldInfoDict_.subOrEmptyDict(fieldName);
return fieldDict.getOrDefault("min", -GREAT); scalarMinMax limits;
}
fieldDict.readIfPresent("min", limits.min());
fieldDict.readIfPresent("max", limits.max());
template<class Type> return limits;
Foam::scalar Foam::gltfSetWriter<Type>::getFieldMax
(
const word& fieldName
) const
{
const dictionary fieldDict = fieldInfoDict_.subOrEmptyDict(fieldName);
return fieldDict.getOrDefault("max", GREAT);
} }
@ -101,69 +95,39 @@ Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getAlphaField
const List<const Field<Type>*>& valueSets const List<const Field<Type>*>& valueSets
) const ) const
{ {
if (dict.found("alpha")) // Fallback value
scalar alphaValue(1);
const entry* eptr = dict.findEntry("alpha", keyType::LITERAL);
if (!eptr)
{ {
// Not specified
}
else if (!eptr->stream().peek().isString())
{
// Value specified
ITstream& is = eptr->stream();
is >> alphaValue;
dict.checkITstream(is, "alpha");
}
else
{
// Enumeration
const auto option = fieldOptionNames_.get("alpha", dict); const auto option = fieldOptionNames_.get("alpha", dict);
switch (option) switch (option)
{ {
case fieldOption::NONE:
{
break;
}
case fieldOption::UNIFORM: case fieldOption::UNIFORM:
{ {
const scalar value = dict.getScalar("alphaValue"); dict.readEntry("alphaValue", alphaValue);
return tmp<scalarField>::New(valueSets[0]->size(), value); break;
}
case fieldOption::FIELD:
{
const word alphaFieldName = dict.get<word>("alphaField");
const bool normalise = dict.get<bool>("normalise");
const label i = valueSetNames.find(alphaFieldName);
if (i == -1)
{
FatalErrorInFunction
<< "Unable to find field " << alphaFieldName
<< ". Valid field names are:" << valueSetNames
<< exit(FatalError);
}
auto tresult =
tmp<scalarField>::New(valueSets[i]->component(0));
if (normalise)
{
tresult.ref() /= mag(tresult() + ROOTVSMALL);
}
return tresult;
}
}
}
return tmp<scalarField>::New(valueSets[0]->size(), Zero);
}
template<class Type>
Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getTrackAlphaField
(
const dictionary& dict,
const wordList& valueSetNames,
const List<List<Field<Type>>>& valueSets,
const label tracki
) const
{
if (dict.found("alpha"))
{
const auto option = fieldOptionNames_.get("alpha", dict);
switch (option)
{
case fieldOption::UNIFORM:
{
const scalar value = dict.getScalar("alphaValue");
return tmp<scalarField>::New
(
valueSets[0][tracki].size(), value
);
} }
case fieldOption::FIELD: case fieldOption::FIELD:
{ {
@ -178,12 +142,9 @@ Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getTrackAlphaField
<< exit(FatalError); << exit(FatalError);
} }
// Note: selecting the first component! const Field<Type>& alphaFld = *(valueSets[fieldi]);
auto tresult =
tmp<scalarField>::New auto tresult = tmp<scalarField>::New(alphaFld.component(0));
(
valueSets[fieldi][tracki].component(0)
);
if (normalise) if (normalise)
{ {
@ -195,7 +156,82 @@ Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getTrackAlphaField
} }
} }
return tmp<scalarField>::New(valueSets[0][tracki].size(), Zero); return tmp<scalarField>::New(1, alphaValue);
}
template<class Type>
Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getTrackAlphaField
(
const dictionary& dict,
const wordList& valueSetNames,
const List<List<Field<Type>>>& valueSets,
const label tracki
) const
{
// Fallback value
scalar alphaValue(1);
const entry* eptr = dict.findEntry("alpha", keyType::LITERAL);
if (!eptr)
{
// Not specified
}
else if (!eptr->stream().peek().isString())
{
// Value specified
ITstream& is = eptr->stream();
is >> alphaValue;
dict.checkITstream(is, "alpha");
}
else
{
// Enumeration
const auto option = fieldOptionNames_.get("alpha", dict);
switch (option)
{
case fieldOption::NONE:
{
break;
}
case fieldOption::UNIFORM:
{
dict.readEntry("alphaValue", alphaValue);
break;
}
case fieldOption::FIELD:
{
const word alphaFieldName = dict.get<word>("alphaField");
const bool normalise = dict.get<bool>("normalise");
const label fieldi = valueSetNames.find(alphaFieldName);
if (fieldi == -1)
{
FatalErrorInFunction
<< "Unable to find field " << alphaFieldName
<< ". Valid field names are:" << valueSetNames
<< exit(FatalError);
}
const Field<Type>& alphaFld = valueSets[fieldi][tracki];
// Note: selecting the first component!
auto tresult = tmp<scalarField>::New(alphaFld.component(0));
if (normalise)
{
tresult.ref() /= mag(tresult() + ROOTVSMALL);
}
return tresult;
}
}
}
return tmp<scalarField>::New(1, alphaValue);
} }
@ -215,17 +251,47 @@ Foam::vector Foam::gltfSetWriter<Type>::getTrackAnimationColour
<< abort(FatalError); << abort(FatalError);
} }
const auto option = fieldOptionNames_.get("colour", animationDict_); const dictionary& dict = animationDict_;
// Fallback value
vector colourValue(Zero);
const entry* eptr = dict.findEntry("colour", keyType::LITERAL);
if (!eptr || !eptr->isStream())
{
FatalIOErrorInFunction(dict)
<< "Missing 'colour' entry"
<< exit(FatalIOError);
}
else if (!eptr->stream().peek().isString())
{
// Value specified
ITstream& is = eptr->stream();
is >> colourValue;
dict.checkITstream(is, "colour");
}
else
{
// Enumeration
const auto option = fieldOptionNames_.get("colour", dict);
switch (option) switch (option)
{ {
case fieldOption::NONE:
{
break;
}
case fieldOption::UNIFORM: case fieldOption::UNIFORM:
{ {
return animationDict_.get<vector>("colourValue"); dict.readEntry("colourValue", colourValue);
break;
} }
case fieldOption::FIELD: case fieldOption::FIELD:
{ {
const word fieldName = animationDict_.get<word>("colourField"); const word fieldName = dict.get<word>("colourField");
const label fieldi = valueSetNames.find(fieldName); const label fieldi = valueSetNames.find(fieldName);
if (fieldi == -1) if (fieldi == -1)
{ {
@ -235,52 +301,42 @@ Foam::vector Foam::gltfSetWriter<Type>::getTrackAnimationColour
<< exit(FatalError); << exit(FatalError);
} }
// Note: selecting the first component! const Field<Type>& colourFld = valueSets[fieldi][tracki];
scalar minValue;
scalar maxValue; scalar refValue(0);
if (!animationDict_.readIfPresent("min", minValue)) scalarMinMax valLimits;
if (pTraits<Type>::nComponents == 1)
{ {
minValue = min(valueSets[fieldi][tracki].component(0)); MinMax<Type> scanned(minMax(colourFld));
refValue = scalar(component(colourFld[0], 0));
valLimits.min() = scalar(component(scanned.min(), 0));
valLimits.max() = scalar(component(scanned.max(), 0));
} }
if (!animationDict_.readIfPresent("max", maxValue)) else
{ {
maxValue = max(valueSets[fieldi][tracki].component(0)); // Use mag() for multiple components
refValue = mag(colourFld[0]);
valLimits = minMaxMag(colourFld);
} }
const scalar refValue = component(valueSets[fieldi][tracki][0], 0);
dict.readIfPresent("min", valLimits.min());
dict.readIfPresent("max", valLimits.max());
const scalar fraction = const scalar fraction =
(refValue - minValue)/(maxValue - minValue + ROOTVSMALL); (
(refValue - valLimits.min())
/ (valLimits.max() - valLimits.min() + ROOTVSMALL)
);
return (colours.value(max(0, min(1, fraction)))); return colours.value(fraction); // 0-1 clipped by value()
}
} }
} }
return vector::zero; return colourValue;
}
template<class Type>
Foam::tmp<Foam::vectorField> Foam::gltfSetWriter<Type>::directions
(
const coordSet& points
) const
{
auto tresult = tmp<vectorField>::New(points.size(), Zero);
auto& result = tresult.ref();
if (points.size() > 1)
{
for (label i = 1; i < points.size(); ++i)
{
result[i-1] = points[i] - points[i-1];
result[i-1].normalise();
}
result.last() = result[points.size()-2];
}
return tresult;
} }
@ -313,8 +369,7 @@ Foam::gltfSetWriter<Type>::gltfSetWriter(const dictionary& dict)
// colourMap coolToWarm; // others... // colourMap coolToWarm; // others...
// min 10; // min 10;
// max 100; // max 100;
// alpha field; // uniform|field // alpha 0.5;
// alphaField ageOfAir;
// } // }
// } // }
} }
@ -363,45 +418,61 @@ void Foam::gltfSetWriter<Type>::write
{ {
const auto& field = *valueSets[fieldi]; const auto& field = *valueSets[fieldi];
const word& fieldName = valueSetNames[fieldi]; const word& fieldName = valueSetNames[fieldi];
const dictionary dict = fieldInfoDict_.subOrEmptyDict(fieldName); const dictionary dict = fieldInfoDict_.subOrEmptyDict(fieldName);
const auto& colours = getColourTable(dict); const auto& colours = getColourTable(dict);
const auto talpha = const auto talpha = getAlphaField(dict, valueSetNames, valueSets);
getAlphaField(dict, valueSetNames, valueSets);
const scalarField& alpha = talpha(); const scalarField& alpha = talpha();
const Type maxValue = max(field); const scalarMinMax valLimits = getFieldLimits(fieldName);
const Type minValue = min(field);
const scalar minValueLimit = getFieldMin(fieldName); // Generated field colours
const scalar maxValueLimit = getFieldMax(fieldName);
for (direction cmpti=0; cmpti < pTraits<Type>::nComponents; ++cmpti)
{
vectorField fieldColour(field.size()); vectorField fieldColour(field.size());
scalarMinMax fldLimits;
if (pTraits<Type>::nComponents == 1)
{
MinMax<Type> scanned(minMax(field));
fldLimits.min() = scalar(component(scanned.min(), 0));
fldLimits.max() = scalar(component(scanned.max(), 0));
}
else
{
// Use mag() for multiple components
fldLimits = minMaxMag(field);
}
const scalar minf = max(fldLimits.min(), valLimits.min());
const scalar maxf = min(fldLimits.max(), valLimits.max());
const scalar deltaf = (maxf - minf + SMALL);
forAll(field, i) forAll(field, i)
{ {
const Type& v = field[i]; const Type& val = field[i];
float f = component(v, cmpti);
float minf = max(component(minValue, cmpti), minValueLimit);
float maxf = min(component(maxValue, cmpti), maxValueLimit);
float deltaf = (maxf - minf + SMALL);
fieldColour[i] = const scalar f =
colours.value(min(max((f - minf)/deltaf, 0), 1)); (
pTraits<Type>::nComponents == 1
? scalar(component(val, 0))
: scalar(mag(val))
);
// 0-1 clipped by value()
fieldColour[i] = colours.value((f - minf)/deltaf);
} }
scene.addColourToMesh scene.addColourToMesh
( (
fieldColour, fieldColour,
"Colour:" + fieldName + Foam::name(cmpti), "Colour:" + fieldName,
meshi, meshi,
alpha alpha
); );
} }
} }
}
scene.write(os); scene.write(os);
} }
@ -482,6 +553,7 @@ void Foam::gltfSetWriter<Type>::writeStaticTracks
{ {
const auto& field = valueSets[fieldi][tracki]; const auto& field = valueSets[fieldi][tracki];
const word& fieldName = valueSetNames[fieldi]; const word& fieldName = valueSetNames[fieldi];
const dictionary dict = const dictionary dict =
fieldInfoDict_.subOrEmptyDict(fieldName); fieldInfoDict_.subOrEmptyDict(fieldName);
const auto& colours = getColourTable(dict); const auto& colours = getColourTable(dict);
@ -490,46 +562,56 @@ void Foam::gltfSetWriter<Type>::writeStaticTracks
getTrackAlphaField(dict, valueSetNames, valueSets, tracki); getTrackAlphaField(dict, valueSetNames, valueSets, tracki);
const scalarField& alpha = talpha(); const scalarField& alpha = talpha();
const Type maxValue = max(field); const scalarMinMax valLimits = getFieldLimits(fieldName);
const Type minValue = min(field);
const scalar minValueLimit = getFieldMin(fieldName);
const scalar maxValueLimit = getFieldMax(fieldName);
for // Generated field colours
( vectorField fieldColour(field.size());
direction cmpti=0;
cmpti < pTraits<Type>::nComponents; scalarMinMax fldLimits;
++cmpti
) if (pTraits<Type>::nComponents == 1)
{ {
vectorField fieldColour(field.size(), Zero); MinMax<Type> scanned(minMax(field));
fldLimits.min() = scalar(component(scanned.min(), 0));
fldLimits.max() = scalar(component(scanned.max(), 0));
}
else
{
// Use mag() for multiple components
fldLimits = minMaxMag(field);
}
const scalar minf = max(fldLimits.min(), valLimits.min());
const scalar maxf = min(fldLimits.max(), valLimits.max());
const scalar deltaf = (maxf - minf + SMALL);
forAll(field, i) forAll(field, i)
{ {
const Type& v = field[i]; const Type& val = field[i];
float f = component(v, cmpti);
float minf =
max(component(minValue, cmpti), minValueLimit);
float maxf =
min(component(maxValue, cmpti), maxValueLimit);
float deltaf = (maxf - minf + SMALL);
fieldColour[i] = const scalar f =
colours.value(min(max((f - minf)/deltaf, 0), 1)); (
pTraits<Type>::nComponents == 1
? scalar(component(val, 0))
: scalar(mag(val))
);
// 0-1 clipped by value()
fieldColour[i] = colours.value((f - minf)/deltaf);
} }
scene.addColourToMesh scene.addColourToMesh
( (
fieldColour, fieldColour,
"Colour:" + fieldName + Foam::name(cmpti), "Colour:" + fieldName,
meshi, meshi,
alpha alpha
); );
} }
} }
} }
}
scene.write(os); scene.write(os);
} }
@ -611,7 +693,7 @@ void Foam::gltfSetWriter<Type>::writeAnimateTracks
scene.addColourToMesh scene.addColourToMesh
( (
vectorField(1, colour), vectorField(1, colour),
"Colour:fixed", "Colour:fixed", // ... or "Colour:constant"
meshi, meshi,
scalarField(1, alpha[0]) scalarField(1, alpha[0])
); );

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021 OpenCFD Ltd. Copyright (C) 2021-2022 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -60,9 +60,11 @@ Description
min 0; min 0;
max 1; max 1;
// Alpha channel [optional] (uniform | field) // Alpha channel [optional] (<scalar> | uniform | field)
alpha uniform; alpha 0.5;
alphaValue 0.5;
//alpha uniform;
//alphaValue 0.5;
//alpha field; //alpha field;
//alphaField T; //alphaField T;
@ -89,9 +91,11 @@ Description
// Colour map [optional] // Colour map [optional]
colourMap <colourMap>; colourMap <colourMap>;
// Colour [optional] (uniform | field) // Colour [optional] (<vector> | uniform | field)
colour uniform; colour (1 0 0); // RGB in range [0-1]
colourValue (1 0 0); // RGB in range [0-1]
//colour uniform;
//colourValue (1 0 0); // RGB in range [0-1]
//colour field; //colour field;
//colourField d; //colourField d;
@ -102,9 +106,11 @@ Description
min 0; min 0;
max 1; max 1;
// Alpha channel [optional] (uniform | field) // Alpha channel [optional] (<scalar> | uniform | field)
alpha uniform; alpha 0.5;
alphaValue 0.5;
//alpha uniform;
//alphaValue 0.5;
//alpha field; //alpha field;
//alphaField T; //alphaField T;
@ -131,6 +137,7 @@ SourceFiles
#include "writer.H" #include "writer.H"
#include "colourTable.H" #include "colourTable.H"
#include "MinMax.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -151,10 +158,11 @@ public:
// Enumerations // Enumerations
//- Field option used for colours //- Field option used for colours
enum class fieldOption enum class fieldOption : char
{ {
NONE, //!< Placeholder type (unnamed)
UNIFORM, //!< Uniform value UNIFORM, //!< Uniform value
FIELD //!< field value FIELD //!< Field value
}; };
@ -187,11 +195,8 @@ private:
//- Return the colour table corresponding to the colour map //- Return the colour table corresponding to the colour map
const colourTable& getColourTable(const dictionary& dict) const; const colourTable& getColourTable(const dictionary& dict) const;
//- Return the field minimum value //- Return the named min/max field limits (from sub-dictionary)
scalar getFieldMin(const word& fieldName) const; scalarMinMax getFieldLimits(const word& fieldName) const;
//- Return the field maximum value
scalar getFieldMax(const word& fieldName) const;
//- Return the alpha field for mesh values //- Return the alpha field for mesh values
tmp<scalarField> getAlphaField tmp<scalarField> getAlphaField
@ -219,9 +224,6 @@ private:
const label tracki const label tracki
) const; ) const;
//- Return track orientation/dirrections
tmp<vectorField> directions(const coordSet& points) const;
public: public: