view Framework/Scene2DViewport/MeasureToolsToolbox.cpp @ 700:059e1fd05fd6 refactor-viewport-controller

Introduced the ViewportController that sits between the application and the Scene2D to handle the trackers and measuring tools. This is a work in progress. The Scene2D is no longer an observable. Message sending is managed by the ViewportController. Move some refs to shared and weak to prevent lifetime issues.
author Benjamin Golinvaux <bgo@osimis.io>
date Sun, 19 May 2019 16:26:17 +0200
parents 8b6adfb62a2f
children c0fcb2757b0a 712ff6ff3c19
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2019 Osimis S.A., Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero 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
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/

#include "MeasureToolsToolbox.h"

#include <Framework/Scene2D/TextSceneLayer.h>

#include <boost/math/constants/constants.hpp>

namespace
{
  double g_pi = boost::math::constants::pi<double>();
}

namespace OrthancStone
{
  double RadiansToDegrees(double angleRad)
  {
    static const double factor = 180.0 / g_pi;
    return angleRad * factor;
  }

  void AddSquare(PolylineSceneLayer::Chain& chain,
    const Scene2D&      scene,
    const ScenePoint2D& centerS,
    const double&       sideLength)
  {
    chain.clear();
    chain.reserve(4);
    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
    //TODO: take DPI into account 
    double handleLX = centerC.GetX() - sideLength / 2;
    double handleTY = centerC.GetY() - sideLength / 2;
    double handleRX = centerC.GetX() + sideLength / 2;
    double handleBY = centerC.GetY() + sideLength / 2;
    ScenePoint2D LTC(handleLX, handleTY);
    ScenePoint2D RTC(handleRX, handleTY);
    ScenePoint2D RBC(handleRX, handleBY);
    ScenePoint2D LBC(handleLX, handleBY);

    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());

    chain.push_back(startLT);
    chain.push_back(startRT);
    chain.push_back(startRB);
    chain.push_back(startLB);
  }
#if 0
  void AddArc(
      PolylineSceneLayer::Chain& chain
    , const Scene2D&      scene
    , const ScenePoint2D& p1
    , const ScenePoint2D& c
    , const ScenePoint2D& p2
    , const double&       radiusS
    , const bool          clockwise
    , const int           subdivisionsCount)
  {
    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
    AddArc(
      chain, scene, c, radiusS, p1cAngle, p2cAngle, 
      clockwise, subdivisionsCount);
  }
#endif

  void AddShortestArc(
      PolylineSceneLayer::Chain& chain
    , const Scene2D&             scene
    , const ScenePoint2D&        p1
    , const ScenePoint2D&        c
    , const ScenePoint2D&        p2
    , const double&              radiusS
    , const int                  subdivisionsCount)
  {
    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
    AddShortestArc(
      chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
  }

  void GetPositionOnBisectingLine(
    ScenePoint2D&       result
    , const ScenePoint2D& p1
    , const ScenePoint2D& c
    , const ScenePoint2D& p2
    , const double d)
  {
    // TODO: fix correct half-plane
    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
    double angle = 0.5*(p1cAngle + p2cAngle);
    double unitVectorX = cos(angle);
    double unitVectorY = sin(angle);
    double posX = c.GetX() + d * unitVectorX;
    double posY = c.GetX() + d * unitVectorY;
    result = ScenePoint2D(posX, posY);
  }
   
  void AddShortestArc(
      PolylineSceneLayer::Chain&  chain
    , const Scene2D&              scene
    , const ScenePoint2D&         centerS
    , const double&               radiusS
    , const double                startAngleRad
    , const double                endAngleRad
    , const int                   subdivisionsCount)
  {
    // this gives a signed difference between angle which
    // is the smallest difference (in magnitude) between 
    // the angles
    double delta = NormalizeAngle(endAngleRad-startAngleRad);

    chain.clear();
    chain.reserve(subdivisionsCount + 1);

    double angleIncr = delta/static_cast<double>(subdivisionsCount);

    double theta = startAngleRad;
    for (int i = 0; i < subdivisionsCount + 1; ++i)
    {
      double offsetX = radiusS * cos(theta);
      double offsetY = radiusS * sin(theta);
      double pointX = centerS.GetX() + offsetX;
      double pointY = centerS.GetY() + offsetY;
      chain.push_back(ScenePoint2D(pointX, pointY));
      theta += angleIncr;
    }
  }

#if 0
  void AddArc(
      PolylineSceneLayer::Chain& chain
    , const Scene2D&      scene
    , const ScenePoint2D& centerS
    , const double&       radiusS
    , const double        startAngleRad
    , const double        endAngleRad
    , const bool          clockwise
    , const int           subdivisionsCount)
  {
    double startAngleRadN = NormalizeAngle(startAngleRad);
    double endAngleRadN = NormalizeAngle(endAngleRad);

    double angle1Rad = std::min(startAngleRadN, endAngleRadN);
    double angle2Rad = std::max(startAngleRadN, endAngleRadN);

    // now we are sure angle1Rad < angle2Rad
    // this means that if we draw from 1 to 2, it will be clockwise (
    // increasing angles).
    // let's fix this:
    if (!clockwise)
    {
      angle2Rad -= 2 * g_pi;
      // now we are sure angle2Rad < angle1Rad (since they were normalized) 
      // and, thus, going from 1 to 2 means the angle values will DECREASE,
      // which is the definition of anticlockwise
    }

    chain.clear();
    chain.reserve(subdivisionsCount + 1);

    double angleIncr = (angle2Rad - angle1Rad)
      / static_cast<double>(subdivisionsCount);

    double theta = angle1Rad;
    for (int i = 0; i < subdivisionsCount + 1; ++i)
    {
      double offsetX = radiusS * cos(theta);
      double offsetY = radiusS * sin(theta);
      double pointX = centerS.GetX() + offsetX;
      double pointY = centerS.GetY() + offsetY;
      chain.push_back(ScenePoint2D(pointX, pointY));
      theta += angleIncr;
    }
  }
#endif

  void AddCircle(PolylineSceneLayer::Chain& chain,
    const Scene2D&      scene,
    const ScenePoint2D& centerS,
    const double&       radiusS,
    const int           numSubdivisions)
  {
    //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
    //TODO: take DPI into account

    // TODO: automatically compute the number for segments for smooth 
    // display based on the radius in pixels.

    chain.clear();
    chain.reserve(numSubdivisions);

    double angleIncr = (2.0 * g_pi)
      / static_cast<double>(numSubdivisions);

    double theta = 0;
    for (int i = 0; i < numSubdivisions; ++i)
    {
      double offsetX = radiusS * cos(theta);
      double offsetY = radiusS * sin(theta);
      double pointX = centerS.GetX() + offsetX;
      double pointY = centerS.GetY() + offsetY;
      chain.push_back(ScenePoint2D(pointX, pointY));
      theta += angleIncr;
    }
  }

  double NormalizeAngle(double angle)
  {
    double retAngle = angle;
    while (retAngle < -1.0*g_pi)
      retAngle += 2 * g_pi;
    while (retAngle >= g_pi)
      retAngle -= 2 * g_pi;
    return retAngle;
  }

  double MeasureAngle(const ScenePoint2D& p1, const ScenePoint2D& c, const ScenePoint2D& p2)
  {
    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
    double delta = p2cAngle - p1cAngle;
    return NormalizeAngle(delta);
  }


#if 0
  void AddEllipse(PolylineSceneLayer::Chain& chain,
    const Scene2D&      scene,
    const ScenePoint2D& centerS,
    const double&       halfHAxis,
    const double&       halfVAxis)
  {
    chain.clear();
    chain.reserve(4);
    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
    //TODO: take DPI into account
    double handleLX = centerC.GetX() - sideLength / 2;
    double handleTY = centerC.GetY() - sideLength / 2;
    double handleRX = centerC.GetX() + sideLength / 2;
    double handleBY = centerC.GetY() + sideLength / 2;
    ScenePoint2D LTC(handleLX, handleTY);
    ScenePoint2D RTC(handleRX, handleTY);
    ScenePoint2D RBC(handleRX, handleBY);
    ScenePoint2D LBC(handleLX, handleBY);

    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());

    chain.push_back(startLT);
    chain.push_back(startRT);
    chain.push_back(startRB);
    chain.push_back(startLB);
}
#endif


  namespace
  {
    /**
    Helper function for outlined text rendering
    */
    TextSceneLayer* GetOutlineTextLayer(
      Scene2D& scene, int baseLayerIndex, int index)
    {
      assert(scene.HasLayer(baseLayerIndex));
      assert(index >= 0);
      assert(index < 5);

      ISceneLayer * layer = &(scene.GetLayer(baseLayerIndex + index));
      TextSceneLayer * concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
      assert(concreteLayer != NULL);
      return concreteLayer;
    }
  }
   
  void SetTextLayerOutlineProperties(
    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p)
  {
    double xoffsets[5] = { 2, 0, -2, 0, 0 };
    double yoffsets[5] = { 0, -2, 0, 2, 0 };

    // get the scaling factor 
    const double pixelToScene =
      scene.GetCanvasToSceneTransform().ComputeZoom();

    for (int i = 0; i < 5; ++i)
    {
      TextSceneLayer* textLayer = GetOutlineTextLayer(scene, baseLayerIndex, i);
      textLayer->SetText(text);

      if (i == 4)
        textLayer->SetColor(0, 223, 81);
      else
        textLayer->SetColor(0, 56, 21);

      ScenePoint2D textAnchor;
      //GetPositionOnBisectingLine(
      //  textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene);
      textLayer->SetPosition(
        p.GetX() + xoffsets[i] * pixelToScene,
        p.GetY() + yoffsets[i] * pixelToScene);
    }
  }








}