scene.C 11.1 KB
Newer Older
1 2 3 4
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
5
    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
     \\/     M anipulation  |
-------------------------------------------------------------------------------
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/>.

\*---------------------------------------------------------------------------*/

// OpenFOAM includes
#include "scene.H"
#include "Constant.H"

// VTK includes
#include "vtkCamera.h"
#include "vtkCubeSource.h"
#include "vtkLightKit.h"
#include "vtkPolyDataMapper.h"
#include "vtkPNGWriter.h"
#include "vtkRenderer.h"
#include "vtkRendererCollection.h"
#include "vtkRenderWindow.h"
#include "vtkWindowToImageFilter.h"


// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

44 45 46 47
void Foam::functionObjects::runTimePostPro::scene::readCamera
(
    const dictionary& dict
)
48 49 50 51 52
{
    if (dict.readIfPresent("nFrameTotal", nFrameTotal_))
    {
        if (nFrameTotal_ < 1)
        {
53 54
            FatalIOErrorInFunction(dict)
                << "nFrameTotal must be 1 or greater"
55 56 57 58
                << exit(FatalIOError);
        }
    }

59
    if (dict.readIfPresent("startPosition", startPosition_))
60
    {
61
        if ((startPosition_ < 0) || (startPosition_ > 1))
62
        {
63 64
            FatalIOErrorInFunction(dict)
                << "startPosition must be in the range 0-1"
65 66
                << exit(FatalIOError);
        }
67 68 69 70
        else
        {
            position_ = startPosition_;
        }
71 72 73 74 75 76 77
    }

    if (nFrameTotal_ > 1)
    {
        scalar endPosition = dict.lookupOrDefault<scalar>("endPosition", 1);
        if ((endPosition < 0) || (endPosition > 1))
        {
78 79
            FatalIOErrorInFunction(dict)
                << "endPosition must be in the range 0-1"
80 81
                << exit(FatalIOError);
        }
82
        dPosition_ = (endPosition - startPosition_)/scalar(nFrameTotal_ - 1);
83 84
    }

85 86 87
    cameraPosition_ = Function1<vector>::New("position", dict);
    cameraFocalPoint_ = Function1<point>::New("focalPoint", dict);
    cameraUp_ = Function1<vector>::New("up", dict);
88

89 90 91
    dict.readIfPresent("clipBox", clipBox_);
    dict.lookup("parallelProjection") >> parallelProjection_;
    if (!parallelProjection_)
92
    {
93
        if (dict.found("viewAngle"))
94
        {
95
            cameraViewAngle_ = Function1<scalar>::New("viewAngle", dict);
96
        }
97
        else
98
        {
99
            cameraViewAngle_.reset
100
            (
101
                new Function1Types::Constant<scalar>("viewAngle", 35.0)
102 103 104 105
            );
        }
    }

106 107 108 109 110 111 112 113 114 115 116
    if (dict.found("zoom"))
    {
        cameraZoom_ = Function1<scalar>::New("zoom", dict);
    }
    else
    {
        cameraZoom_.reset
        (
            new Function1Types::Constant<scalar>("zoom", 1.0)
        );
    }
117 118 119
}


120 121 122 123
void Foam::functionObjects::runTimePostPro::scene::readColours
(
    const dictionary& dict
)
124 125 126 127 128
{
    const wordList colours = dict.toc();
    forAll(colours, i)
    {
        const word& c = colours[i];
129
        colours_.insert(c, Function1<vector>::New(c, dict).ptr());
130 131 132 133
    }
}


134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
void Foam::functionObjects::runTimePostPro::scene::setActorVisibility
(
    vtkRenderer* renderer,
    const bool visible
) const
{
    vtkActorCollection *actors = renderer->GetActors();
    for (int i = 0; i < actors->GetNumberOfItems(); ++i)
    {
        vtkActor *actor = vtkActor::SafeDownCast(actors->GetItemAsObject(i));
        actor->SetVisibility(visible);
    }
}


149 150 151 152 153
void Foam::functionObjects::runTimePostPro::scene::initialise
(
    vtkRenderer* renderer,
    const word& outputName
)
154 155
{
    currentFrameI_ = 0;
156
    position_ = startPosition_;
157 158 159 160

    outputName_ = outputName;

    // Set the background
161
    const vector backgroundColour = colours_["background"]->value(position_);
162 163 164 165 166 167 168 169 170 171 172
    renderer->SetBackground
    (
        backgroundColour.x(),
        backgroundColour.y(),
        backgroundColour.z()
    );

    // Apply gradient background if "background2" defined
    if (colours_.found("background2"))
    {
        renderer->GradientBackgroundOn();
173
        vector backgroundColour2 = colours_["background2"]->value(position_);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

        renderer->SetBackground2
        (
            backgroundColour2.x(),
            backgroundColour2.y(),
            backgroundColour2.z()
        );
    }

    // Depth peeling
    renderer->SetUseDepthPeeling(true);
    renderer->SetMaximumNumberOfPeels(4);
    renderer->SetOcclusionRatio(0);

    // Set the camera
189
    auto camera = vtkSmartPointer<vtkCamera>::New();
190 191 192
    camera->SetParallelProjection(parallelProjection_);
    renderer->SetActiveCamera(camera);

193
    // Add the lights
194
    auto lightKit = vtkSmartPointer<vtkLightKit>::New();
195 196
    lightKit->AddLightsToRenderer(renderer);

197
    if (!clipBox_.empty())
198 199 200
    {
        const point& min = clipBox_.min();
        const point& max = clipBox_.max();
201
        auto clipBox = vtkSmartPointer<vtkCubeSource>::New();
202 203 204 205 206 207 208 209 210
        clipBox->SetXLength(max.x() - min.x());
        clipBox->SetYLength(max.y() - min.y());
        clipBox->SetZLength(max.z() - min.z());
        clipBox->SetCenter
        (
            min.x() + 0.5*(max.x() - min.x()),
            min.y() + 0.5*(max.y() - min.y()),
            min.z() + 0.5*(max.z() - min.z())
        );
211
        auto clipMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
212 213
        clipMapper->SetInputConnection(clipBox->GetOutputPort());

214 215 216 217
        clipBoxActor_ = vtkSmartPointer<vtkActor>::New();
        clipBoxActor_->SetMapper(clipMapper);
        clipBoxActor_->VisibilityOff();
        renderer->AddActor(clipBoxActor_);
218 219 220 221
    }
}


222 223 224 225
void Foam::functionObjects::runTimePostPro::scene::setCamera
(
    vtkRenderer* renderer
) const
226
{
227 228 229
    vtkCamera* camera = renderer->GetActiveCamera();

    if (parallelProjection_)
230
    {
231 232
        // Restore parallel scale to allow application of zoom (later)
        camera->SetParallelScale(1);
233
    }
234
    else
235 236 237
    {
        // Restore viewAngle (it might be reset by clipping)
        camera->SetViewAngle(cameraViewAngle_->value(position_));
238
    }
239 240 241 242 243 244 245 246 247 248 249 250 251 252

    const vector up = cameraUp_->value(position_);
    const vector pos = cameraPosition_->value(position_);
    const point focalPoint = cameraFocalPoint_->value(position_);
    const scalar zoom = cameraZoom_->value(position_);

    camera->SetViewUp(up.x(), up.y(), up.z());
    camera->SetPosition(pos.x(), pos.y(), pos.z());
    camera->SetFocalPoint(focalPoint.x(), focalPoint.y(), focalPoint.z());


    // Apply clipping if required
    // Note: possible optimisation - if the camera is static, this only needs
    //       to be done once on initialisation
253
    if (!clipBox_.empty())
254
    {
255
        setActorVisibility(renderer, false);
256
        clipBoxActor_->VisibilityOn();
257 258

        // Call ResetCamera() to fit clip box in view
259
        renderer->ResetCamera();
260 261

        setActorVisibility(renderer, true);
262 263 264 265 266 267 268
        clipBoxActor_->VisibilityOff();
    }

    // Zoom applied after all other operations
    camera->Zoom(zoom);

    camera->Modified();
269 270 271
}


272 273
Foam::string
Foam::functionObjects::runTimePostPro::scene::frameIndexStr() const
274 275 276 277 278 279 280 281 282 283
{
    string str = Foam::name(currentFrameI_);
    str.insert(0, 4 - str.length(), '0');

    return str;
}


// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

284 285 286 287 288
Foam::functionObjects::runTimePostPro::scene::scene
(
    const objectRegistry& obr,
    const word& name
)
289 290 291 292
:
    obr_(obr),
    name_(name),
    colours_(),
Andrew Heather's avatar
Andrew Heather committed
293 294 295 296
    cameraPosition_(nullptr),
    cameraFocalPoint_(nullptr),
    cameraUp_(nullptr),
    cameraViewAngle_(nullptr),
297
    cameraZoom_(nullptr),
298
    clipBox_(boundBox::invertedBox),
299
    clipBoxActor_(),
300 301
    parallelProjection_(true),
    nFrameTotal_(1),
302
    startPosition_(0),
303 304 305 306 307 308 309 310 311
    position_(0),
    dPosition_(0),
    currentFrameI_(0),
    outputName_("unknown")
{}


// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //

312
Foam::functionObjects::runTimePostPro::scene::~scene()
313 314 315 316 317
{}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

318
const Foam::HashPtrTable<Foam::Function1<Foam::vector>, Foam::word>&
319
Foam::functionObjects::runTimePostPro::scene::colours() const
320 321 322 323 324
{
    return colours_;
}


325
Foam::label Foam::functionObjects::runTimePostPro::scene::frameIndex() const
326 327 328 329 330
{
    return currentFrameI_;
}


331
Foam::scalar Foam::functionObjects::runTimePostPro::scene::position() const
332 333 334 335 336
{
    return position_;
}


337 338 339 340
void Foam::functionObjects::runTimePostPro::scene::read
(
    const dictionary& dict
)
341 342 343 344 345 346
{
    readCamera(dict.subDict("camera"));
    readColours(dict.subDict("colours"));
}


347
bool Foam::functionObjects::runTimePostPro::scene::loop(vtkRenderer* renderer)
348 349
{
    static bool initialised = false;
350
    setCamera(renderer);
351 352 353 354 355 356 357

    if (!initialised)
    {
        initialised = true;
        return true;
    }

358 359 360 361
    // Ensure that all objects can be seen without clipping
    // Note: can only be done after all objects have been added!
    renderer->ResetCameraClippingRange();

362 363 364 365 366
    // Save image from last iteration
    saveImage(renderer->GetRenderWindow());

    currentFrameI_++;

367
    position_ = startPosition_ + currentFrameI_*dPosition_;
368

369 370 371 372 373 374
    if (currentFrameI_ < nFrameTotal_)
    {
        return true;
    }
    else
    {
375
        initialised = false;
376 377 378 379 380
        return false;
    }
}


381 382 383 384
void Foam::functionObjects::runTimePostPro::scene::saveImage
(
    vtkRenderWindow* renderWindow
) const
385 386 387 388 389 390
{
    if (!renderWindow)
    {
        return;
    }

391 392
    const Time& runTime = obr_.time();

393 394 395 396 397 398 399 400 401 402 403
    const fileName relPath
    (
        functionObject::outputPrefix/name_/obr_.time().timeName()
    );

    fileName prefix
    (
        Pstream::parRun() ?
            runTime.path()/".."/relPath
          : runTime.path()/relPath
    );
404

405 406 407 408 409
    mkDir(prefix);

    renderWindow->Render();

    // Set up off-screen rendering
410
    auto windowToImageFilter = vtkSmartPointer<vtkWindowToImageFilter>::New();
411 412 413 414 415 416 417 418 419 420 421

    windowToImageFilter->SetInput(renderWindow);

    //// Add alpha channel for transparency
    // windowToImageFilter->SetInputBufferTypeToRGBA();
    windowToImageFilter->SetInputBufferTypeToRGB();

//    windowToImageFilter->ReadFrontBufferOff();
    windowToImageFilter->Update();

    // Save the image
422
    auto writer = vtkSmartPointer<vtkPNGWriter>::New();
423 424 425 426 427 428 429 430 431 432 433
    fileName fName(prefix/outputName_ + '.' + frameIndexStr() + ".png");
    writer->SetFileName(fName.c_str());
    writer->SetInputConnection(windowToImageFilter->GetOutputPort());

    Info<< "    Generating image: " << fName << endl;

    writer->Write();
}


// ************************************************************************* //