/*========================================================================= Program: ParaView Module: $RCSfile$ Copyright (c) Kitware, Inc. All rights reserved. See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkPVWebApplication.h" #include "vtkBase64Utilities.h" #include "vtkCamera.h" #include "vtkCommand.h" #include "vtkDataEncoder.h" #include "vtkImageData.h" #include "vtkJPEGWriter.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPNGWriter.h" #include "vtkPointData.h" #include "vtkPVRenderView.h" #include "vtkRendererCollection.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkSmartPointer.h" #include "vtkSMContextViewProxy.h" #include "vtkSMPropertyHelper.h" #include "vtkSMRenderViewProxy.h" #include "vtkSMViewProxy.h" #include "vtkTimerLog.h" #include "vtkUnsignedCharArray.h" #include "vtkWebGLExporter.h" #include "vtkWebGLObject.h" #include "vtkWebInteractionEvent.h" #include #include #include class vtkPVWebApplication::vtkInternals { public: struct ImageCacheValueType { public: vtkSmartPointer Data; bool NeedsRender; bool HasImagesBeingProcessed; vtkObject* ViewPointer; unsigned long ObserverId; ImageCacheValueType() : NeedsRender(true), HasImagesBeingProcessed(false), ViewPointer(NULL), 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 = NULL; } } 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; }; vtkStandardNewMacro(vtkPVWebApplication); //---------------------------------------------------------------------------- vtkPVWebApplication::vtkPVWebApplication(): ImageEncoding(ENCODING_BASE64), ImageCompression(COMPRESSION_JPEG), Internals(new vtkPVWebApplication::vtkInternals()) { } //---------------------------------------------------------------------------- vtkPVWebApplication::~vtkPVWebApplication() { delete this->Internals; this->Internals = NULL; } //---------------------------------------------------------------------------- bool vtkPVWebApplication::GetHasImagesBeingProcessed(vtkSMViewProxy* view) { const vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view]; return value.HasImagesBeingProcessed; } //---------------------------------------------------------------------------- vtkUnsignedCharArray* vtkPVWebApplication::InteractiveRender(vtkSMViewProxy* view, int quality) { // for now, just do the same as StillRender(). return this->StillRender(view, quality); } //---------------------------------------------------------------------------- void vtkPVWebApplication::InvalidateCache(vtkSMViewProxy* view) { this->Internals->ImageCache[view].NeedsRender = true; } //---------------------------------------------------------------------------- vtkUnsignedCharArray* vtkPVWebApplication::StillRender(vtkSMViewProxy* view, int quality) { if (!view) { vtkErrorMacro("No view specified."); return NULL; } vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view]; value.SetListener(view); if (value.NeedsRender == false && value.Data != NULL && view->GetNeedsUpdate() == false) { //cout << "Reusing cache" << endl; bool latest = this->Internals->Encoder->GetLatestOutput(view->GetGlobalID(), value.Data); value.HasImagesBeingProcessed = !latest; return value.Data; } //cout << "Regenerating " << endl; //vtkTimerLog::ResetLog(); //vtkTimerLog::CleanupLog(); //vtkTimerLog::MarkStartEvent("StillRenderToString"); //vtkTimerLog::MarkStartEvent("CaptureWindow"); // 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. vtkImageData* image = view->CaptureWindow(1); image->GetDimensions(this->LastStillRenderImageSize); //vtkTimerLog::MarkEndEvent("CaptureWindow"); //vtkTimerLog::MarkEndEvent("StillRenderToString"); //vtkTimerLog::DumpLogWithIndents(&cout, 0.0); this->Internals->Encoder->PushAndTakeReference(view->GetGlobalID(), image, quality); assert(image == NULL); if (value.Data == NULL) { // we need to wait till output is processed. //cout << "Flushing" << endl; this->Internals->Encoder->Flush(view->GetGlobalID()); //cout << "Done Flushing" << endl; } bool latest = this->Internals->Encoder->GetLatestOutput(view->GetGlobalID(), value.Data); value.HasImagesBeingProcessed = !latest; value.NeedsRender = false; return value.Data; } //---------------------------------------------------------------------------- const char* vtkPVWebApplication::StillRenderToString(vtkSMViewProxy* view, unsigned long time, int quality) { vtkUnsignedCharArray* array = this->StillRender(view, quality); if (array && array->GetMTime() != time) { this->LastStillRenderToStringMTime = array->GetMTime(); //cout << "Image size: " << array->GetNumberOfTuples() << endl; return reinterpret_cast(array->GetPointer(0)); } return NULL; } //---------------------------------------------------------------------------- bool vtkPVWebApplication::HandleInteractionEvent( vtkSMViewProxy* view, vtkWebInteractionEvent* event) { vtkRenderWindowInteractor *iren = NULL; if (view->GetRenderWindow()) { iren = view->GetRenderWindow()->GetInteractor(); } else { vtkErrorMacro("Interaction not supported for view : " << view); return false; } int viewSize[2]; vtkSMPropertyHelper(view, "ViewSize").Get(viewSize, 2); int posX = std::floor(viewSize[0] * event->GetX() + 0.5); int posY = std::floor(viewSize[1] * event->GetY() + 0.5); int ctrlKey = (event->GetModifiers() & vtkWebInteractionEvent::CTRL_KEY) != 0? 1: 0; int shiftKey = (event->GetModifiers() & vtkWebInteractionEvent::SHIFT_KEY) != 0? 1: 0; iren->SetEventInformation(posX, posY, ctrlKey, shiftKey, event->GetKeyCode()); 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(); } else { iren->LeftButtonReleaseEvent(); } } if ( (changed_buttons & vtkWebInteractionEvent::RIGHT_BUTTON) != 0 ) { if ( (event->GetButtons() & vtkWebInteractionEvent::RIGHT_BUTTON) != 0) { iren->RightButtonPressEvent(); } else { iren->RightButtonReleaseEvent(); } } if ( (changed_buttons & vtkWebInteractionEvent::MIDDLE_BUTTON) != 0 ) { if ( (event->GetButtons() & vtkWebInteractionEvent::MIDDLE_BUTTON) != 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* vtkPVWebApplication::GetWebGLSceneMetaData(vtkSMViewProxy* view) { if (!view) { vtkErrorMacro("No view specified."); return NULL; } vtkRenderWindow* renWin = view->GetRenderWindow(); vtkPVRenderView* pvRenderView = vtkPVRenderView::SafeDownCast(view->GetClientSideView()); if(renWin == NULL || pvRenderView == NULL) { vtkErrorMacro("The view is supported for WebGL export: " << view); return NULL; } // We use the camera focal point to be the center of rotation double centerOfRotation[3]; vtkCamera *cam = pvRenderView->GetActiveCamera(); cam->GetFocalPoint(centerOfRotation); if(this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end()) { this->Internals->ViewWebGLMap[view] = vtkSmartPointer::New(); } vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view]; webglExporter->parseScene( renWin->GetRenderers(), view->GetGlobalIDAsString(),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* vtkPVWebApplication::GetWebGLBinaryData( vtkSMViewProxy* view, const char* id, int part) { if (!view) { vtkErrorMacro("No view specified."); return NULL; } if(this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end()) { if(this->GetWebGLSceneMetaData(view) == NULL) { vtkErrorMacro("Failed to generate WebGL MetaData for: " << view); return NULL; } } vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view]; if(webglExporter == NULL) { vtkErrorMacro("There is no cached WebGL Exporter for: " << view); return NULL; } 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 NULL; } //---------------------------------------------------------------------------- void vtkPVWebApplication::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "ImageEncoding: " << this->ImageEncoding << endl; os << indent << "ImageCompression: " << this->ImageCompression << endl; }