/*========================================================================= Program: Visualization Toolkit Module: vtkVRPanelRepresentation.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 "vtkVRPanelRepresentation.h" #include "vtkCamera.h" #include "vtkEventData.h" #include "vtkObjectFactory.h" #include "vtkQuaternion.h" #include "vtkRenderer.h" #include "vtkTextActor3D.h" #include "vtkTextProperty.h" #include "vtkTransform.h" #include "vtkVRRenderWindow.h" // TODO // - add option to remove crop planes vtkStandardNewMacro(vtkVRPanelRepresentation); //------------------------------------------------------------------------------ vtkVRPanelRepresentation::vtkVRPanelRepresentation() { this->TextActor = vtkTextActor3D::New(); this->TextActor->GetTextProperty()->SetFontSize(17); this->Text = "This is a Panel Widget"; this->TextActor->SetInput(this->Text.c_str()); vtkTextProperty* prop = this->TextActor->GetTextProperty(); this->TextActor->ForceOpaqueOn(); this->TextActor->SetUserMatrix(vtkMatrix4x4::New()); this->TextActor->GetUserMatrix()->Delete(); prop->SetFontFamilyToTimes(); prop->SetFrame(1); prop->SetFrameWidth(12); prop->SetFrameColor(0.0, 0.0, 0.0); prop->SetBackgroundOpacity(0.5); prop->SetBackgroundColor(0.0, 0.0, 0.0); prop->SetFontSize(25); this->InteractionState = vtkVRPanelRepresentation::Outside; this->CoordinateSystem = World; this->AllowAdjustment = true; } //------------------------------------------------------------------------------ vtkVRPanelRepresentation::~vtkVRPanelRepresentation() { this->TextActor->Delete(); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::SetCoordinateSystemToWorld() { if (this->CoordinateSystem == World) { return; } this->TextActor->GetUserMatrix()->Identity(); this->CoordinateSystem = World; this->Modified(); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::SetCoordinateSystemToHMD() { if (this->CoordinateSystem == HMD) { return; } this->CoordinateSystem = HMD; this->Modified(); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::SetCoordinateSystemToLeftController() { if (this->CoordinateSystem == LeftController) { return; } this->CoordinateSystem = LeftController; this->Modified(); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::SetCoordinateSystemToRightController() { if (this->CoordinateSystem == RightController) { return; } this->CoordinateSystem = RightController; this->Modified(); } //------------------------------------------------------------------------------ int vtkVRPanelRepresentation::ComputeComplexInteractionState( vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata, int) { if (!this->AllowAdjustment || this->InteractionState == vtkVRPanelRepresentation::Moving) { return this->InteractionState; } vtkEventData* edata = static_cast(calldata); vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D(); if (edd) { double pos[4]; edd->GetWorldPosition(pos); pos[3] = 1.0; const double* bds = this->TextActor->GetBounds(); double length = sqrt((bds[1] - bds[0]) * (bds[1] - bds[0]) + (bds[3] - bds[2]) * (bds[3] - bds[2]) + (bds[5] - bds[4]) * (bds[5] - bds[4])); double tolerance = length * 0.05; if (pos[0] > bds[0] - tolerance && pos[0] < bds[1] + tolerance && pos[1] > bds[2] - tolerance && pos[1] < bds[3] + tolerance && pos[2] > bds[4] - tolerance && pos[2] < bds[5] + tolerance) { this->InteractionState = vtkVRPanelRepresentation::Moving; } else { this->InteractionState = vtkVRPanelRepresentation::Outside; } } return this->InteractionState; } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::StartComplexInteraction( vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata) { vtkEventData* edata = static_cast(calldata); vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D(); if (edd) { edd->GetWorldPosition(this->StartEventPosition); this->LastEventPosition[0] = this->StartEventPosition[0]; this->LastEventPosition[1] = this->StartEventPosition[1]; this->LastEventPosition[2] = this->StartEventPosition[2]; edd->GetWorldOrientation(this->StartEventOrientation); std::copy( this->StartEventOrientation, this->StartEventOrientation + 4, this->LastEventOrientation); } } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::ComplexInteraction( vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata) { vtkEventData* edata = static_cast(calldata); vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D(); if (edd) { double eventPos[3]; edd->GetWorldPosition(eventPos); double eventDir[4]; edd->GetWorldOrientation(eventDir); // Process the motion if (this->InteractionState == vtkVRPanelRepresentation::Moving) { // this->TranslateOutline(this->LastEventPosition, eventPos); this->UpdatePose(this->LastEventPosition, this->LastEventOrientation, eventPos, eventDir); } // Book keeping this->LastEventPosition[0] = eventPos[0]; this->LastEventPosition[1] = eventPos[1]; this->LastEventPosition[2] = eventPos[2]; std::copy(eventDir, eventDir + 4, this->LastEventOrientation); this->Modified(); } } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::EndComplexInteraction( vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void*) { this->InteractionState = vtkVRPanelRepresentation::Outside; } //------------------------------------------------------------------------------ // Loop through all points and translate and rotate them void vtkVRPanelRepresentation::UpdatePose(double* p1, double* orient1, double* p2, double* orient2) { if (this->CoordinateSystem == World) { this->UpdatePropPose(this->TextActor, p1, orient1, p2, orient2); return; } if (this->CoordinateSystem == HMD) { vtkMatrix4x4* mat = this->TextActor->GetUserMatrix(); mat->Register(this); this->TextActor->SetUserMatrix(nullptr); this->TempMatrix->DeepCopy(mat); this->TempMatrix->Invert(); double p14[4]; std::copy(p1, p1 + 3, p14); p14[3] = 1.0; double p24[4]; std::copy(p2, p2 + 3, p24); p24[3] = 1.0; this->TempMatrix->MultiplyPoint(p14, p14); this->TempMatrix->MultiplyPoint(p24, p24); double trans[3]; for (int i = 0; i < 3; i++) { trans[i] = p24[i] - p14[i]; } vtkTransform* newTransform = this->TempTransform; // changes in Z adjust the scale double ratio = (0.5 + trans[2] / this->LastScale) / 0.5; double* scale = this->TextActor->GetScale(); this->TextActor->SetScale(scale[0] * ratio, scale[1] * ratio, scale[2] * ratio); this->TextActor->AddPosition(trans[0], trans[1], 0.0); // compute the net rotation vtkQuaternion q1; q1.SetRotationAngleAndAxis( vtkMath::RadiansFromDegrees(orient1[0]), orient1[1], orient1[2], orient1[3]); vtkQuaternion q2; q2.SetRotationAngleAndAxis( vtkMath::RadiansFromDegrees(orient2[0]), orient2[1], orient2[2], orient2[3]); q1.Conjugate(); q2 = q2 * q1; double axis[4]; axis[0] = vtkMath::DegreesFromRadians(q2.GetRotationAngleAndAxis(axis + 1)); newTransform->Identity(); newTransform->PostMultiply(); newTransform->Concatenate(this->TempMatrix); newTransform->TransformNormal(axis + 1, axis + 1); vtkMatrix4x4* oldMatrix = this->TempMatrix; this->TextActor->GetMatrix(oldMatrix); newTransform->Identity(); newTransform->PostMultiply(); newTransform->Concatenate(oldMatrix); newTransform->Translate(-(p14[0]), -(p14[1]), -(p14[2])); newTransform->RotateWXYZ(axis[0], axis[1], axis[2], axis[3]); newTransform->Translate(p14[0], p14[1], p14[2]); this->TextActor->SetPosition(newTransform->GetPosition()); this->TextActor->SetOrientation(newTransform->GetOrientation()); this->TextActor->SetUserMatrix(mat); mat->UnRegister(this); } if (this->CoordinateSystem == LeftController || this->CoordinateSystem == RightController) { vtkMatrix4x4* mat = this->TextActor->GetUserMatrix(); mat->Register(this); this->TextActor->SetUserMatrix(nullptr); this->TempMatrix->DeepCopy(mat); this->TempMatrix->Invert(); double p14[4]; std::copy(p1, p1 + 3, p14); p14[3] = 1.0; double p24[4]; std::copy(p2, p2 + 3, p24); p24[3] = 1.0; this->TempMatrix->MultiplyPoint(p14, p14); this->TempMatrix->MultiplyPoint(p24, p24); double trans[3]; for (int i = 0; i < 3; i++) { trans[i] = p24[i] - p14[i]; } this->TextActor->AddPosition(trans[0], trans[1], trans[2]); vtkTransform* newTransform = this->TempTransform; // compute the net rotation vtkQuaternion q1; q1.SetRotationAngleAndAxis( vtkMath::RadiansFromDegrees(orient1[0]), orient1[1], orient1[2], orient1[3]); vtkQuaternion q2; q2.SetRotationAngleAndAxis( vtkMath::RadiansFromDegrees(orient2[0]), orient2[1], orient2[2], orient2[3]); q1.Conjugate(); q2 = q2 * q1; double axis[4]; axis[0] = vtkMath::DegreesFromRadians(q2.GetRotationAngleAndAxis(axis + 1)); newTransform->Identity(); newTransform->PostMultiply(); newTransform->Concatenate(this->TempMatrix); newTransform->TransformNormal(axis + 1, axis + 1); vtkMatrix4x4* oldMatrix = this->TempMatrix; this->TextActor->GetMatrix(oldMatrix); newTransform->Identity(); newTransform->PostMultiply(); newTransform->Concatenate(oldMatrix); newTransform->Translate(-(p14[0]), -(p14[1]), -(p14[2])); newTransform->RotateWXYZ(axis[0], axis[1], axis[2], axis[3]); newTransform->Translate(p14[0], p14[1], p14[2]); this->TextActor->SetPosition(newTransform->GetPosition()); this->TextActor->SetOrientation(newTransform->GetOrientation()); this->TextActor->SetUserMatrix(mat); mat->UnRegister(this); } } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::ReleaseGraphicsResources(vtkWindow* w) { this->TextActor->ReleaseGraphicsResources(w); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::ComputeMatrix(vtkRenderer* ren) { // check whether or not need to rebuild the matrix // only rebuild on left eye otherwise we get two different // poses for two eyes vtkCamera* cam = ren->GetActiveCamera(); if (this->CoordinateSystem != World && cam->GetLeftEye()) { vtkVRRenderWindow* rw = static_cast(ren->GetVTKWindow()); if (this->CoordinateSystem == HMD) { vtkTransform* vt = cam->GetViewTransformObject(); vt->GetInverse(this->TextActor->GetUserMatrix()); if (rw->GetPhysicalScale() != this->LastScale) { double ratio = rw->GetPhysicalScale() / this->LastScale; double* scale = this->TextActor->GetScale(); this->TextActor->SetScale(scale[0] * ratio, scale[1] * ratio, scale[2] * ratio); double* pos = this->TextActor->GetPosition(); this->TextActor->SetPosition(pos[0] * ratio, pos[1] * ratio, -0.5 * rw->GetPhysicalScale()); this->LastScale = rw->GetPhysicalScale(); } else { double* pos = this->TextActor->GetPosition(); this->TextActor->SetPosition(pos[0], pos[1], -0.5 * rw->GetPhysicalScale()); } } vtkEventDataDevice device = this->CoordinateSystem == LeftController ? vtkEventDataDevice::LeftController : vtkEventDataDevice::RightController; vtkNew deviceToWorldMatrix; if (rw->GetDeviceToWorldMatrixForDevice(device, deviceToWorldMatrix)) { // transform the text to the devices location in world coordinates this->TextActor->GetUserMatrix()->DeepCopy(deviceToWorldMatrix); } } } //------------------------------------------------------------------------------ int vtkVRPanelRepresentation::RenderOpaqueGeometry(vtkViewport* v) { if (!this->GetVisibility()) { return 0; } // make sure the device has the same matrix // if (true /* HMD coords * /) { vtkRenderer* ren = static_cast(v); this->ComputeMatrix(ren); } int count = this->TextActor->RenderOpaqueGeometry(v); return count; } //------------------------------------------------------------------------------ int vtkVRPanelRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* v) { if (!this->GetVisibility()) { return 0; } int count = this->TextActor->RenderTranslucentPolygonalGeometry(v); return count; } //------------------------------------------------------------------------------ vtkTypeBool vtkVRPanelRepresentation::HasTranslucentPolygonalGeometry() { if (!this->GetVisibility()) { return 0; } int result = 0; result |= this->TextActor->HasTranslucentPolygonalGeometry(); return result; } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::PlaceWidget(double bds[6]) { this->TextActor->GetUserMatrix()->Identity(); if (this->CoordinateSystem == World) { // center the planel this->TextActor->SetPosition( 0.5 * (bds[0] + bds[1]), 0.5 * (bds[2] + bds[3]), 0.5 * (bds[4] + bds[5])); double length = sqrt((bds[1] - bds[0]) * (bds[1] - bds[0]) + (bds[3] - bds[2]) * (bds[3] - bds[2]) + (bds[5] - bds[4]) * (bds[5] - bds[4])); this->TextActor->SetScale(length / 700.0, length / 700.0, length / 700.0); this->LastScale = length; } if (this->CoordinateSystem != World) { double scale = sqrt((bds[1] - bds[0]) * (bds[1] - bds[0]) + (bds[3] - bds[2]) * (bds[3] - bds[2]) + (bds[5] - bds[4]) * (bds[5] - bds[4])); this->TextActor->SetScale(scale / 700.0, scale / 700.0, scale / 700.0); this->LastScale = scale; this->TextActor->SetPosition(0.0, 0.0, -0.5 * scale); } } void vtkVRPanelRepresentation::PlaceWidgetExtended( const double* bds, const double* normal, const double* upvec, double scale) { this->TextActor->GetUserMatrix()->Identity(); this->TextActor->SetOrientation(0, 0, 0); this->LastScale = scale; // grab the bounding box of the text // so we can position and scale to that int tbounds[4]; this->TextActor->GetBoundingBox(tbounds); int maxdim = tbounds[1] - tbounds[0]; if (tbounds[3] - tbounds[2] > maxdim) { maxdim = tbounds[3] - tbounds[2]; } // should always be at least 50 pixels // for any reasonable string. maxdim = maxdim > 50 ? maxdim : 50.0; // make the normal ortho to upvec vtkVector3d nup(upvec); nup.Normalize(); vtkVector3d nvpn(normal); nvpn.Normalize(); vtkVector3d nvright = nup.Cross(nvpn); nvright.Normalize(); nvpn = nvright.Cross(nup); double basis[16]; basis[0] = nvright[0]; basis[4] = nvright[1]; basis[8] = nvright[2]; basis[12] = 0; basis[1] = nup[0]; basis[5] = nup[1]; basis[9] = nup[2]; basis[13] = 0; basis[2] = nvpn[0]; basis[6] = nvpn[1]; basis[10] = nvpn[2]; basis[14] = 0; basis[3] = 0; basis[7] = 0; basis[11] = 0; basis[15] = 1; vtkNew basisT; basisT->SetMatrix(basis); this->TextActor->SetOrientation(basisT->GetOrientation()); if (this->CoordinateSystem == World) { // center the planel and default size to 400cm this->TextActor->SetScale(0.4 * scale / maxdim, 0.4 * scale / maxdim, 0.4 * scale / maxdim); this->TextActor->SetPosition( 0.5 * (bds[0] + bds[1]), 0.5 * (bds[2] + bds[3]), 0.5 * (bds[4] + bds[5])); } if (this->CoordinateSystem == LeftController || this->CoordinateSystem == RightController) { // position the planel and default size to 400cm this->TextActor->SetScale(0.4 / maxdim, 0.4 / maxdim, 0.4 / maxdim); this->TextActor->SetPosition(0.5 * (bds[0] + bds[1]) - 0.2 * (tbounds[1] - tbounds[0]) / maxdim, 0.5 * (bds[2] + bds[3]), 0.5 * (bds[4] + bds[5])); } if (this->CoordinateSystem == HMD) { // center the planel and default size to 400cm this->TextActor->SetScale(0.4 * scale / maxdim, 0.4 * scale / maxdim, 0.4 * scale / maxdim); this->TextActor->SetPosition(-0.2 * (tbounds[1] - tbounds[0]) * scale / maxdim, -0.2 * (tbounds[3] - tbounds[2]) * scale / maxdim, -0.5 * scale); } } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::BuildRepresentation() { // if (this->GetMTime() > this->BuildTime) // { // this->TextActor3D->SetPosition(tpos); // // scale should cover 10% of FOV // double fov = ren->GetActiveCamera()->GetViewAngle(); // double tsize = 0.1*2.0*atan(fov*0.5); // 10% of fov // tsize /= 200.0; // about 200 pixel texture map // scale *= tsize; // this->TextActor3D->SetScale(scale, scale, scale); // this->TextActor3D->SetInput(text.c_str()); // this->BuildTime.Modified(); // } } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } //------------------------------------------------------------------------------ void vtkVRPanelRepresentation::SetText(const char* text) { if (this->Text == text) { return; } this->Text = text; this->TextActor->SetInput(this->Text.c_str()); this->Modified(); }