/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | Copyright (C) 2015-2019 OpenCFD Ltd. \\/ 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 . \*---------------------------------------------------------------------------*/ // 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 * * * * * * * * * * * // void Foam::functionObjects::runTimePostPro::scene::readCamera ( const dictionary& dict ) { if (dict.readIfPresent("nFrameTotal", nFrameTotal_)) { if (nFrameTotal_ < 1) { FatalIOErrorInFunction(dict) << "nFrameTotal must be 1 or greater" << exit(FatalIOError); } } if (dict.readIfPresent("startPosition", startPosition_)) { if ((startPosition_ < 0) || (startPosition_ > 1)) { FatalIOErrorInFunction(dict) << "startPosition must be in the range 0-1" << exit(FatalIOError); } else { position_ = startPosition_; } } if (nFrameTotal_ > 1) { scalar endPosition = dict.lookupOrDefault("endPosition", 1); if ((endPosition < 0) || (endPosition > 1)) { FatalIOErrorInFunction(dict) << "endPosition must be in the range 0-1" << exit(FatalIOError); } dPosition_ = (endPosition - startPosition_)/scalar(nFrameTotal_ - 1); } cameraPosition_ = Function1::New("position", dict); cameraFocalPoint_ = Function1::New("focalPoint", dict); cameraUp_ = Function1::New("up", dict); dict.readIfPresent("clipBox", clipBox_); dict.readEntry("parallelProjection", parallelProjection_); if (!parallelProjection_) { if (dict.found("viewAngle")) { cameraViewAngle_ = Function1::New("viewAngle", dict); } else { cameraViewAngle_.reset ( new Function1Types::Constant("viewAngle", 35.0) ); } } if (dict.found("zoom")) { cameraZoom_ = Function1::New("zoom", dict); } else { cameraZoom_.reset ( new Function1Types::Constant("zoom", 1.0) ); } } void Foam::functionObjects::runTimePostPro::scene::readColours ( const dictionary& dict ) { const wordList colours(dict.toc()); for (const word& c : colours) { colours_.insert(c, Function1::New(c, dict)); } } void Foam::functionObjects::runTimePostPro::scene::setActorVisibility ( vtkRenderer* renderer, const bool visible ) const { if (!renderer) return; vtkActorCollection *actors = renderer->GetActors(); for (int i = 0; i < actors->GetNumberOfItems(); ++i) { vtkActor *actor = vtkActor::SafeDownCast(actors->GetItemAsObject(i)); actor->SetVisibility(visible); } } void Foam::functionObjects::runTimePostPro::scene::initialise ( vtkRenderer* renderer, const word& outputName ) { currentFrameI_ = 0; position_ = startPosition_; outputName_ = outputName; if (!renderer) return; // Set the background const vector bgColour = colours_["background"]->value(position_); renderer->SetBackground(bgColour.x(), bgColour.y(), bgColour.z()); // Apply gradient background if "background2" defined if (colours_.found("background2")) { const vector bg2Colour = colours_["background2"]->value(position_); renderer->GradientBackgroundOn(); renderer->SetBackground2(bg2Colour.x(), bg2Colour.y(), bg2Colour.z()); } else if (Pstream::parRun()) { // Oddly enough we seem a gradient background for parallel rendering, // otherwise the colours look quite funny. // Doesn't seem to matter if we use SetBackground2() though renderer->GradientBackgroundOn(); renderer->SetBackground2(bgColour.x(), bgColour.y(), bgColour.z()); } // Depth peeling renderer->SetUseDepthPeeling(true); renderer->SetMaximumNumberOfPeels(4); renderer->SetOcclusionRatio(0); // Set the camera auto camera = vtkSmartPointer::New(); camera->SetParallelProjection(parallelProjection_); renderer->SetActiveCamera(camera); // Add the lights auto lightKit = vtkSmartPointer::New(); lightKit->AddLightsToRenderer(renderer); if (!clipBox_.empty()) { const point& min = clipBox_.min(); const point& max = clipBox_.max(); auto clipBox = vtkSmartPointer::New(); 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()) ); auto clipMapper = vtkSmartPointer::New(); clipMapper->SetInputConnection(clipBox->GetOutputPort()); clipBoxActor_ = vtkSmartPointer::New(); clipBoxActor_->SetMapper(clipMapper); clipBoxActor_->VisibilityOff(); renderer->AddActor(clipBoxActor_); } } void Foam::functionObjects::runTimePostPro::scene::setCamera ( vtkRenderer* renderer ) const { if (!renderer) return; vtkCamera* camera = renderer->GetActiveCamera(); if (parallelProjection_) { // Restore parallel scale to allow application of zoom (later) camera->SetParallelScale(1); } else { // Restore viewAngle (it might be reset by clipping) camera->SetViewAngle(cameraViewAngle_->value(position_)); } 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 if (!clipBox_.empty()) { setActorVisibility(renderer, false); clipBoxActor_->VisibilityOn(); // Call ResetCamera() to fit clip box in view renderer->ResetCamera(); setActorVisibility(renderer, true); clipBoxActor_->VisibilityOff(); } // Zoom applied after all other operations camera->Zoom(zoom); camera->Modified(); } Foam::string Foam::functionObjects::runTimePostPro::scene::frameIndexStr() const { string str = Foam::name(currentFrameI_); str.insert(0, 4 - str.length(), '0'); return str; } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::functionObjects::runTimePostPro::scene::scene ( const objectRegistry& obr, const word& name ) : obr_(obr), name_(name), colours_(), cameraPosition_(nullptr), cameraFocalPoint_(nullptr), cameraUp_(nullptr), cameraViewAngle_(nullptr), cameraZoom_(nullptr), clipBox_(boundBox::invertedBox), clipBoxActor_(), parallelProjection_(true), nFrameTotal_(1), startPosition_(0), position_(0), dPosition_(0), currentFrameI_(0), outputName_("unknown") {} // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // Foam::functionObjects::runTimePostPro::scene::~scene() {} // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // const Foam::HashPtrTable, Foam::word>& Foam::functionObjects::runTimePostPro::scene::colours() const { return colours_; } Foam::label Foam::functionObjects::runTimePostPro::scene::frameIndex() const { return currentFrameI_; } Foam::scalar Foam::functionObjects::runTimePostPro::scene::position() const { return position_; } void Foam::functionObjects::runTimePostPro::scene::read ( const dictionary& dict ) { readCamera(dict.subDict("camera")); readColours(dict.subDict("colours")); } bool Foam::functionObjects::runTimePostPro::scene::loop(vtkRenderer* renderer) { static bool initialised = false; setCamera(renderer); if (!initialised) { initialised = true; return true; } if (renderer) { // Ensure that all objects can be seen without clipping // Note: can only be done after all objects have been added! renderer->ResetCameraClippingRange(); // Save image from last iteration saveImage(renderer->GetRenderWindow()); } ++currentFrameI_; position_ = startPosition_ + currentFrameI_*dPosition_; if (currentFrameI_ < nFrameTotal_) { return true; } else { initialised = false; return false; } } void Foam::functionObjects::runTimePostPro::scene::saveImage ( vtkRenderWindow* renderWindow ) const { if (!renderWindow) { return; } const Time& runTime = obr_.time(); const fileName fName ( runTime.globalPath() / functionObject::outputPrefix / name_ / runTime.timeName() / outputName_ + '.' + frameIndexStr() + ".png" ); renderWindow->Render(); // Set up off-screen rendering auto windowToImageFilter = vtkSmartPointer::New(); windowToImageFilter->SetInput(renderWindow); //// Add alpha channel for transparency // windowToImageFilter->SetInputBufferTypeToRGBA(); windowToImageFilter->SetInputBufferTypeToRGB(); // windowToImageFilter->ReadFrontBufferOff(); windowToImageFilter->Update(); // Save the image if (Pstream::master()) { mkDir(fName.path()); auto writer = vtkSmartPointer::New(); writer->SetFileName(fName.c_str()); writer->SetInputConnection(windowToImageFilter->GetOutputPort()); Info<< " Generating image: " << runTime.relativePath(fName) << endl; writer->Write(); } } // ************************************************************************* //