/*========================================================================= Program: Visualization Toolkit Module: vtkWebApplication.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkWebApplication.h" #include "vtkBase64Utilities.h" #include "vtkCamera.h" #include "vtkCommand.h" #include "vtkImageData.h" #include "vtkJPEGWriter.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkObjectIdMap.h" #include "vtkPNGWriter.h" #include "vtkDataEncoder.h" #include "vtkWebInteractionEvent.h" #include "vtkPointData.h" #include "vtkRenderWindow.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRendererCollection.h" #include "vtkSmartPointer.h" #include "vtkTimerLog.h" #include "vtkUnsignedCharArray.h" #include "vtkWebGLExporter.h" #include "vtkWebGLObject.h" #include "vtkWindowToImageFilter.h" #include #include #include #include class vtkWebApplication::vtkInternals { public: struct ImageCacheValueType { public: vtkSmartPointer Data; bool NeedsRender; bool HasImagesBeingProcessed; vtkObject* ViewPointer; unsigned long ObserverId; ImageCacheValueType() : NeedsRender(true), HasImagesBeingProcessed(false), ViewPointer(nullptr), ObserverId(0) { } void SetListener(vtkObject* view) { if(this->ViewPointer == view) { return; } if(this->ViewPointer && this->ObserverId) { this->ViewPointer->RemoveObserver(this->ObserverId); this->ObserverId = 0; } this->ViewPointer = view; if(this->ViewPointer) { this->ObserverId = this->ViewPointer->AddObserver(vtkCommand::AnyEvent, this, &ImageCacheValueType::ViewEventListener); } } void RemoveListener(vtkObject* view) { if(this->ViewPointer && this->ViewPointer == view && this->ObserverId) { this->ViewPointer->RemoveObserver(this->ObserverId); this->ObserverId = 0; this->ViewPointer = nullptr; } } void ViewEventListener(vtkObject*, unsigned long, void*) { this->NeedsRender = true; } }; typedef std::map ImageCacheType; ImageCacheType ImageCache; typedef std::map ButtonStatesType; ButtonStatesType ButtonStates; vtkNew Encoder; // WebGL related struct struct WebGLObjCacheValue { public: int ObjIndex; std::map BinaryParts; }; // map for > typedef std::map WebGLObjId2IndexMap; std::map WebGLExporterObjIdMap; // map for std::map > ViewWebGLMap; std::string LastAllWebGLBinaryObjects; vtkNew ObjectIdMap; }; vtkStandardNewMacro(vtkWebApplication); //---------------------------------------------------------------------------- vtkWebApplication::vtkWebApplication(): ImageEncoding(ENCODING_BASE64), ImageCompression(COMPRESSION_JPEG), Internals(new vtkWebApplication::vtkInternals()) { } //---------------------------------------------------------------------------- vtkWebApplication::~vtkWebApplication() { delete this->Internals; this->Internals = nullptr; } //---------------------------------------------------------------------------- bool vtkWebApplication::GetHasImagesBeingProcessed(vtkRenderWindow* view) { const vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view]; return value.HasImagesBeingProcessed; } //---------------------------------------------------------------------------- vtkUnsignedCharArray* vtkWebApplication::InteractiveRender(vtkRenderWindow* view, int quality) { // for now, just do the same as StillRender(). return this->StillRender(view, quality); } //---------------------------------------------------------------------------- void vtkWebApplication::InvalidateCache(vtkRenderWindow* view) { this->Internals->ImageCache[view].NeedsRender = true; } //---------------------------------------------------------------------------- vtkUnsignedCharArray* vtkWebApplication::StillRender(vtkRenderWindow* view, int quality) { if (!view) { vtkErrorMacro("No view specified."); return nullptr; } vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view]; value.SetListener(view); if (value.NeedsRender == false && value.Data != nullptr /* FIXME SEB && view->HasDirtyRepresentation() == false */) { //cout << "Reusing cache" << endl; bool latest = this->Internals->Encoder->GetLatestOutput(this->Internals->ObjectIdMap->GetGlobalId(view), value.Data); value.HasImagesBeingProcessed = !latest; return value.Data; } //cout << "Regenerating " << endl; //vtkTimerLog::ResetLog(); //vtkTimerLog::CleanupLog(); //vtkTimerLog::MarkStartEvent("StillRenderToString"); //vtkTimerLog::MarkStartEvent("CaptureWindow"); view->Render(); // TODO: We should add logic to check if a new rendering needs to be done and // then alone do a new rendering otherwise use the cached image. vtkNew w2i; w2i->SetInput(view); w2i->SetScale(1); w2i->ReadFrontBufferOff(); w2i->ShouldRerenderOff(); w2i->FixBoundaryOn(); w2i->Update(); vtkImageData* image = vtkImageData::New(); image->ShallowCopy(w2i->GetOutput()); //vtkTimerLog::MarkEndEvent("CaptureWindow"); //vtkTimerLog::MarkEndEvent("StillRenderToString"); //vtkTimerLog::DumpLogWithIndents(&cout, 0.0); this->Internals->Encoder->PushAndTakeReference(this->Internals->ObjectIdMap->GetGlobalId(view), image, quality); assert(image == nullptr); if (value.Data == nullptr) { // we need to wait till output is processed. //cout << "Flushing" << endl; this->Internals->Encoder->Flush(this->Internals->ObjectIdMap->GetGlobalId(view)); //cout << "Done Flushing" << endl; } bool latest = this->Internals->Encoder->GetLatestOutput(this->Internals->ObjectIdMap->GetGlobalId(view), value.Data); value.HasImagesBeingProcessed = !latest; value.NeedsRender = false; return value.Data; } //---------------------------------------------------------------------------- const char* vtkWebApplication::StillRenderToString(vtkRenderWindow* view, vtkMTimeType time, int quality) { vtkUnsignedCharArray* array = this->StillRender(view, quality); if (array && array->GetMTime() != time) { this->LastStillRenderToMTime = array->GetMTime(); //cout << "Image size: " << array->GetNumberOfTuples() << endl; return reinterpret_cast(array->GetPointer(0)); } return nullptr; } //---------------------------------------------------------------------------- bool vtkWebApplication::HandleInteractionEvent( vtkRenderWindow* view, vtkWebInteractionEvent* event) { vtkRenderWindowInteractor *iren = nullptr; if (view) { iren = view->GetInteractor(); } else { vtkErrorMacro("Interaction not supported for view : " << view); return false; } int ctrlKey = (event->GetModifiers() & vtkWebInteractionEvent::CTRL_KEY) != 0? 1: 0; int shiftKey = (event->GetModifiers() & vtkWebInteractionEvent::SHIFT_KEY) != 0? 1: 0; // Handle scroll action if any if(event->GetScroll()) { iren->SetEventInformation(0, 0, ctrlKey, shiftKey, event->GetKeyCode(), 0); iren->MouseMoveEvent(); iren->RightButtonPressEvent(); iren->SetEventInformation(0, event->GetScroll()*10, ctrlKey, shiftKey, event->GetKeyCode(), 0); iren->MouseMoveEvent(); iren->RightButtonReleaseEvent(); this->Internals->ImageCache[view].NeedsRender = true; return true; } int *viewSize = view->GetSize(); int posX = std::floor(viewSize[0] * event->GetX() + 0.5); int posY = std::floor(viewSize[1] * event->GetY() + 0.5); iren->SetEventInformation(posX, posY, ctrlKey, shiftKey, event->GetKeyCode(), event->GetRepeatCount()); unsigned int prev_buttons = this->Internals->ButtonStates[view]; unsigned int changed_buttons = (event->GetButtons() ^ prev_buttons); iren->MouseMoveEvent(); if ( (changed_buttons & vtkWebInteractionEvent::LEFT_BUTTON) != 0 ) { if ( (event->GetButtons() & vtkWebInteractionEvent::LEFT_BUTTON) != 0) { iren->LeftButtonPressEvent(); if(event->GetRepeatCount() > 0) { iren->LeftButtonReleaseEvent(); } } else { iren->LeftButtonReleaseEvent(); } } if ( (changed_buttons & vtkWebInteractionEvent::RIGHT_BUTTON) != 0 ) { if ( (event->GetButtons() & vtkWebInteractionEvent::RIGHT_BUTTON) != 0) { iren->RightButtonPressEvent(); if(event->GetRepeatCount() > 0) { iren->RightButtonPressEvent(); } } else { iren->RightButtonReleaseEvent(); } } if ( (changed_buttons & vtkWebInteractionEvent::MIDDLE_BUTTON) != 0 ) { if ( (event->GetButtons() & vtkWebInteractionEvent::MIDDLE_BUTTON) != 0) { iren->MiddleButtonPressEvent(); if(event->GetRepeatCount() > 0) { iren->MiddleButtonPressEvent(); } } else { iren->MiddleButtonReleaseEvent(); } } this->Internals->ButtonStates[view] = event->GetButtons(); bool needs_render = (changed_buttons != 0 || event->GetButtons()); this->Internals->ImageCache[view].NeedsRender = needs_render; return needs_render; } // --------------------------------------------------------------------------- const char* vtkWebApplication::GetWebGLSceneMetaData(vtkRenderWindow* view) { if (!view) { vtkErrorMacro("No view specified."); return nullptr; } // We use the camera focal point to be the center of rotation double centerOfRotation[3]; vtkCamera *cam = view->GetRenderers()->GetFirstRenderer()->GetActiveCamera(); cam->GetFocalPoint(centerOfRotation); if(this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end()) { this->Internals->ViewWebGLMap[view] = vtkSmartPointer::New(); } std::stringstream globalIdAsString; globalIdAsString << this->Internals->ObjectIdMap->GetGlobalId(view); vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view]; webglExporter->parseScene( view->GetRenderers(), globalIdAsString.str().c_str(), VTK_PARSEALL); vtkInternals::WebGLObjId2IndexMap webglMap; for(int i=0; iGetNumberOfObjects(); ++i) { vtkWebGLObject* wObj = webglExporter->GetWebGLObject(i); if(wObj && wObj->isVisible()) { vtkInternals::WebGLObjCacheValue val; val.ObjIndex = i; for(int j=0; jGetNumberOfParts(); ++j) { val.BinaryParts[j] = ""; } webglMap[wObj->GetId()] = val; } } this->Internals->WebGLExporterObjIdMap[webglExporter] = webglMap; webglExporter->SetCenterOfRotation( static_cast(centerOfRotation[0]), static_cast(centerOfRotation[1]), static_cast(centerOfRotation[2])); return webglExporter->GenerateMetadata(); } //---------------------------------------------------------------------------- const char* vtkWebApplication::GetWebGLBinaryData( vtkRenderWindow* view, const char* id, int part) { if (!view) { vtkErrorMacro("No view specified."); return nullptr; } if(this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end()) { if(this->GetWebGLSceneMetaData(view) == nullptr) { vtkErrorMacro("Failed to generate WebGL MetaData for: " << view); return nullptr; } } vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view]; if(webglExporter == nullptr) { vtkErrorMacro("There is no cached WebGL Exporter for: " << view); return nullptr; } if(this->Internals->WebGLExporterObjIdMap[webglExporter].size() > 0 && this->Internals->WebGLExporterObjIdMap[webglExporter].find(id) != this->Internals->WebGLExporterObjIdMap[webglExporter].end()) { vtkInternals::WebGLObjCacheValue* cachedVal = &(this->Internals->WebGLExporterObjIdMap[webglExporter][id]); if(cachedVal->BinaryParts.find(part) != cachedVal->BinaryParts.end()) { if(cachedVal->BinaryParts[part].empty()) { vtkWebGLObject* obj = webglExporter->GetWebGLObject(cachedVal->ObjIndex); if(obj && obj->isVisible()) { // Manage Base64 vtkNew base64; unsigned char* output = new unsigned char[obj->GetBinarySize(part)*2]; int size = base64->Encode( obj->GetBinaryData(part), obj->GetBinarySize(part), output, false); cachedVal->BinaryParts[part] = std::string((const char *)output, size); delete[] output; } } return cachedVal->BinaryParts[part].c_str(); } } return nullptr; } //---------------------------------------------------------------------------- void vtkWebApplication::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "ImageEncoding: " << this->ImageEncoding << endl; os << indent << "ImageCompression: " << this->ImageCompression << endl; } //---------------------------------------------------------------------------- vtkObjectIdMap* vtkWebApplication::GetObjectIdMap() { return this->Internals->ObjectIdMap; } //---------------------------------------------------------------------------- std::string vtkWebApplication::GetObjectId(vtkObject* obj) { std::ostringstream oss; oss << std::hex << static_cast(obj); return oss.str(); }