// Copyright (c) 2021 The Foundry Visionmongers Ltd. All Rights Reserved.

#include "DDImage/ModifyGeomOp.h"

#include "usg/geom/PointBasedPrim.h"

using namespace DD::Image;

namespace NukeExample {

  static const char* kTwistClass = "GeoTwist";
  static const char* kHelp = "@i;GeoTwist@n; "
    "spins the points around a center point.";

  const char* const kTwistKnobName = "twist";
  const char* const kCenterKnobName = "center";
  const char* const kScaleKnobName = "scale";

  /*! A demonstration GeomOp applying a simple point location modification algorithm.
  */
  class GeoTwist : public ModifyGeomOp
  {
  public:
    const char* Class()     const override { return kTwistClass; }

    const char* node_help() const override { return kHelp; }

    GeoTwist(Node* node) : ModifyGeomOp(node, BuildEngine<Engine>()) {}

    void knobs(Knob_Callback f) override
    {
      ModifyGeomOp::knobs(f);

      double twist = 0.0;
      fdk::Vec3f center(0.0f);
      fdk::Vec3f scale(1.0f);

      Double_knob(f, &twist, IRange(-180, 180), kTwistKnobName, "Twist");
      KnobModifiesAttribValues(f);

      XYZ_knob(f, &center.x, kCenterKnobName, "Rotation Center");
      KnobModifiesAttribValues(f);

      XYZ_knob(f, &scale.x, kScaleKnobName, "Scale");
      KnobModifiesAttribValues(f);
    }

    static Op* Build(Node* node) { return new GeoTwist(node); }
    static const Op::Description description;

  private:
    class Engine : public ModifyEngine
    {
      PointBasedPrimFilterGeomEngineI _filter;

    public:
      Engine(Op* parent) : ModifyEngine(parent, &_filter) {}

      void processPrim(usg::GeomSceneContext& context,
                       const usg::StageRef& srcStage,
                       const usg::Path& primPath) override
      {
        auto inPrim = usg::PointBasedPrim::getInStage(srcStage, primPath);
        if (!inPrim) {
          return; // no point data to twiddle
        }

        if (auto outPrim = usg::PointBasedPrim::overrideInLayer(editLayer(), inPrim)) {
          const usg::Attribute pointsAttr = inPrim.getPointsAttr();
          if (!pointsAttr) {
            return;
          }

          const auto kTwist = knob(kTwistKnobName);
          const auto kCenter = knob(kCenterKnobName);
          const auto kScale = knob(kScaleKnobName);

          // Are the modify controls or input points animating?
          const bool isAnimating = (modifyValuesTarget().isAnimating() || pointsAttr.isAnimating());

          usg::Vec3fArray points;
          for (const auto& time : context.processTimes()) {
            const double twist  = getValue<double>(0.0, kTwist, time);
            const fdk::Vec3f center = getValue<fdk::Vec3f>(fdk::Vec3f(0.0f), kCenter, time);
            const auto scale = getValue<fdk::Vec3f>(fdk::Vec3f(1.0f), kScale, time);

            if (inPrim.getPointsArray(points, time) > 0) {
              for (auto& p : points) {
                const auto pnew = (p - center);

                // Twist factor * distance from center:
                const float r = radiansf(twist) * (1.0f - pnew.length() * 2.0f) * 2.0f;

                // Rotations weighted by distance:
                const float s = sinf(r);
                const float c = cosf(r);

                // Move the point:
                p.x = (pnew.x * c + pnew.y * -s) * scale.x + center.x;
                p.y = (pnew.x * s + pnew.y *  c) * scale.y + center.y;
              }

              const auto outPointsTime = (isAnimating) ? time : fdk::defaultTimeValue();
              outPrim.setPoints(points, outPointsTime);
              outPrim.setBoundsAttr(points, outPointsTime);
            }
          }
        }

      }

    };

  };

  const Op::Description GeoTwist::description(kTwistClass, GeoTwist::Build);
}