All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalismo.ui.rendering.RendererPanel.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016  University of Basel, Graphics and Vision Research Group
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see .
 */

package scalismo.ui.rendering

import java.awt.image.BufferedImage
import java.io.File

import javax.imageio.ImageIO
import scalismo.ui.control.BackgroundColor.event.BackgroundColorChanged
import scalismo.ui.control.{NodeVisibility, SlicingPosition}
import scalismo.ui.model.Scene.event.SceneChanged
import scalismo.ui.model.{Axis, BoundingBox, Renderable}
import scalismo.ui.rendering.RendererPanel.Cameras
import scalismo.ui.rendering.actor._
import scalismo.ui.rendering.actor.mixin.IsImageActor
import scalismo.ui.rendering.internal.RenderingComponent
import scalismo.ui.util.EdtUtil
import scalismo.ui.view.{ViewportPanel, ViewportPanel2D}
import vtk._

import scala.collection.immutable
import scala.swing.{BorderPanel, Component}
import scala.util.Try

object RendererPanel {

  // Helper object to properly set camera positions for 2D slices.
  private[RendererPanel] object Cameras {

    // the state of a freshly created camera.
    case class DefaultCameraState(position: Array[Double], focalPoint: Array[Double], viewUp: Array[Double])

    private var _defaultCameraState: Option[DefaultCameraState] = None

    // this will have an effect exactly once, on the very first invocation.
    def setDefaultCameraState(cam: vtkCamera): Unit = Cameras.synchronized {
      if (_defaultCameraState.isEmpty) {
        _defaultCameraState = Some(DefaultCameraState(cam.GetPosition(), cam.GetFocalPoint(), cam.GetViewUp()))
      }
    }

    def defaultCameraState: DefaultCameraState = _defaultCameraState.get

    case class CameraChangeFromDefault(pitch: Option[Double], roll: Option[Double], yaw: Option[Double])

    def cameraChangeForAxis(axis: Axis): CameraChangeFromDefault = {
      axis match {
        case Axis.Y => CameraChangeFromDefault(Some(-90), None, None)
        case Axis.X => CameraChangeFromDefault(Some(180), Some(-90), Some(270))
        case Axis.Z => CameraChangeFromDefault(Some(180), Some(180), None)
      }
    }
  }

}

class RendererPanel(viewport: ViewportPanel) extends BorderPanel {

  private class RenderableAndActors(val renderable: Renderable, val actorsOption: Option[Actors]) {
    def vtkActors: List[vtkActor] = actorsOption.map(_.vtkActors).getOrElse(Nil)
  }

  private val frame = viewport.frame

  private val implementation = new RenderingComponent(viewport)

  private var attached: Boolean = false

  private var currentActors: List[RenderableAndActors] = Nil

  private var _currentBoundingBox: BoundingBox = BoundingBox.Invalid

  private var updating = false

  def resetCamera(): Unit = {
    implementation.resetCamera()
    render()
  }

  def render(): Unit = {
    implementation.render()
  }

  def screenshot(file: File): Try[Unit] = Try {
    val source = implementation.getComponent
    val image = new BufferedImage(source.getWidth, source.getHeight, BufferedImage.TYPE_INT_RGB)
    val g = image.createGraphics()

    // parameter description: see
    // https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/javax/media/opengl/awt/GLJPanel.html#setupPrint%28double,%20double,%20int,%20int,%20int%29
    source.setupPrint(1, 1, 0, -1, -1)
    source.printAll(g)
    source.releasePrint()

    image.flush()
    ImageIO.write(image, "png", file)
  }

  def setCameraToAxis(axis: Axis): Unit = {
    val cam = implementation.getActiveCamera
    val default = Cameras.defaultCameraState
    cam.SetPosition(default.position)
    cam.SetFocalPoint(default.focalPoint)
    cam.SetViewUp(default.viewUp)

    val change = Cameras.cameraChangeForAxis(axis)
    change.yaw.foreach(v => cam.Azimuth(v))
    change.pitch.foreach(v => cam.Elevation(v))
    change.roll.foreach(v => cam.Roll(v))
    cam.OrthogonalizeViewUp()
    resetCamera()
  }

  def setAttached(attached: Boolean): Unit = {
    this.attached = attached
    updateAllActors()
  }

  def rendererState: RendererState = implementation.rendererState

  def currentBoundingBox: BoundingBox = _currentBoundingBox

  private def currentBoundingBox_=(newBb: BoundingBox): Unit = {
    if (_currentBoundingBox != newBb) {
      _currentBoundingBox = newBb
      // we take a shortcut here, and directly send on behalf of the viewport.
      viewport.publishEvent(ViewportPanel.event.BoundingBoxChanged(viewport))
    }
  }

  // this is a comparatively expensive operation, so it should only be invoked if something "big" has changed.
  private def updateAllActors(): Unit = {
    // just in case: make sure we're on the correct thread to make VTK happy
    if (!updating) EdtUtil.onEdtWait {
      updating = true
      val renderer = implementation.getRenderer
      renderer.SetBackground(frame.sceneControl.backgroundColor.vtkValue)

      val renderables = if (attached) frame.sceneControl.renderablesFor(viewport) else Nil

      val wasEmptyBefore = currentBoundingBox == BoundingBox.Invalid

      val obsolete = currentActors.filter(ra => !renderables.exists(_ eq ra.renderable))

      val missing = renderables.diff(currentActors.map(_.renderable))
      val created = missing.map(r =>
        new RenderableAndActors(r, ActorsFactory.factoryFor(r).flatMap(f => f.untypedActorsFor(r, viewport)))
      )

      if (obsolete.nonEmpty) {
        obsolete.foreach(ra =>
          ra.vtkActors.foreach { actor =>
            renderer.RemoveActor(actor)
            actor match {
              case dyn: ActorEvents =>
                deafTo(dyn)
                dyn.onDestroy()
              case _ => // do nothing
            }
          }
        )
        currentActors = currentActors diff obsolete
      }

      if (created.nonEmpty) {
        created.foreach(_.vtkActors.foreach { actor =>
          actor match {
            case eventActor: ActorEvents => listenTo(eventActor)
            case _                       =>
          }
          renderer.AddActor(actor)
        })
        currentActors = currentActors ++ created
      }

      if (created.nonEmpty || obsolete.nonEmpty) {

        // Actors have changed, we may have to reorder them.
        // We have to add image actors first, so that all other
        // (non-image) actors are properly drawn on them,
        // instead of the images hiding other actors.

        val originalActors: immutable.IndexedSeq[vtkActor] = {
          val actors = renderer.GetActors()
          val count = actors.GetNumberOfItems()
          if (count > 1) {
            actors.InitTraversal()
            (0 until count) map { _ =>
              actors.GetNextActor()
            }
          } else immutable.IndexedSeq()
        }

        def prioritize(a1: vtkActor, a2: vtkActor): Boolean = {
          (a1, a2) match {
            case (_: IsImageActor, _: IsImageActor) => false
            case (_: IsImageActor, _)               => true
            case (_, _)                             => false
          }
        }

        val reorderedActors = originalActors.sortWith(prioritize)

        if (originalActors != reorderedActors) {
          originalActors.foreach(renderer.RemoveActor)
          reorderedActors.foreach(renderer.AddActor)
        }

        geometryChanged()

        if (wasEmptyBefore) {
          resetCamera()
        } else {
          render()
        }

      }

      updating = false

    }
  }

  private def updateBackgroundOnly(): Unit = EdtUtil.onEdtWait {
    implementation.getRenderer.SetBackground(frame.sceneControl.backgroundColor.vtkValue)
    render()
  }

  private def slicingPositionChanged(pc: SlicingPosition.event.PointChanged): Unit = {
    viewport match {
      case vp2d: ViewportPanel2D =>
        val cam = implementation.getActiveCamera()
        val pos = cam.GetPosition()
        val foc = cam.GetFocalPoint()
        vp2d.axis match {
          case Axis.X =>
            val amount = pc.current.x - pc.previous.x
            pos(0) += amount
            foc(0) += amount
          case Axis.Y =>
            val amount = pc.current.y - pc.previous.y
            pos(1) += amount
            foc(1) += amount
          case Axis.Z =>
            val amount = pc.current.z - pc.previous.z
            pos(2) += amount
            foc(2) += amount
        }
        cam.SetPosition(pos)
        cam.SetFocalPoint(foc)
      case _ => // can't handle
    }
  }

  private def geometryChanged(): Unit = {
    currentBoundingBox = currentActors.foldLeft(BoundingBox.Invalid: BoundingBox)({
      case (bb, actors) =>
        bb.union(actors.actorsOption.map(_.boundingBox).getOrElse(BoundingBox.Invalid))
    })
    render()
  }

  // constructor

  Cameras.setDefaultCameraState(implementation.getActiveCamera)

  layout(Component.wrap(implementation.getComponent)) = BorderPanel.Position.Center

  viewport match {
    case _2d: ViewportPanel2D =>
      listenTo(viewport.frame.sceneControl.slicingPosition)
      setCameraToAxis(_2d.axis)
    case _ => // nothing
  }

  listenTo(frame.scene, frame.sceneControl.nodeVisibility, frame.sceneControl.backgroundColor)

  reactions += {
    case SceneChanged(_) if attached           => updateAllActors()
    case BackgroundColorChanged(_) if attached => updateBackgroundOnly()
    case ActorEvents.event.ActorChanged(_, actorGeometryChanged) if attached =>
      if (actorGeometryChanged) {
        geometryChanged()
      } else {
        render()
      }
    case pc @ SlicingPosition.event.PointChanged(_, _, _)                                         => slicingPositionChanged(pc)
    case NodeVisibility.event.NodeVisibilityChanged(_, view) if attached && view == this.viewport => updateAllActors()
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy