import Visiometa
from Visiometa import *
from Visiometa.Base import *
from Visiometa.Base.Math import *
from Visiometa.Main import *

import clr
from clr import *

import System
from System import *

# -----------------------------------------------------------------------------

# Import some lambdas into the global namespace to help with overload
# resolution of .NET methods in IronPython.

_typeFunction = Type.GetType(
  'Visiometa.Base.Math.Function, '
  'Visiometa.Base, Version=1.0.0.0, '
  'Culture=neutral, '
  'PublicKeyToken=null')

_minReal2 = _typeFunction.GetMethod('Min', Array[Type]([Real2, Real2]))
minReal2 = lambda x, y : (_minReal2.Invoke(None, Array[object]([x, y])))

_maxReal2 = _typeFunction.GetMethod('Max', Array[Type]([Real2, Real2]))
maxReal2 = lambda x, y : (_maxReal2.Invoke(None, Array[object]([x, y])))

_compMaxReal2 = _typeFunction.GetMethod('ComponentMax', Array[Type]([Real2]))
compMaxReal2 = lambda x : (_compMaxReal2.Invoke(None, Array[object]([x])))

# -----------------------------------------------------------------------------

# Creates an orthographic camera projection matrix for the given aspect ratio
# and size with default near and far planes.

def CreateOrthoMatrix(
  aspectRatio,
  orthographicSize) :

  farPlane = 10000.0
  nearPlane = 0.01
  a = 2.0 / (orthographicSize * aspectRatio)
  b = 2.0 / orthographicSize
  c = 2.0 / (farPlane - nearPlane)
  d = (farPlane + nearPlane) / (farPlane - nearPlane)

  return Real4x4(
      a, 0.0, 0.0, 0.0,
    0.0,   b, 0.0, 0.0,
    0.0, 0.0,   c,   d,
    0.0, 0.0, 0.0, 1.0)

# -----------------------------------------------------------------------------

def WorldToView(
  worldPosition,
  cameraTransform,
  workspaceBasis) :

  viewSpaceTransform = Real4x4.FromRows(
    Real4(workspaceBasis.Rightward, 0.0),
    Real4(workspaceBasis.Upward, 0.0),
    Real4(-workspaceBasis.Forward, 0.0),
    Real4(0.0, 0.0, 0.0, 1.0))

  viewMatrix = Function.ProductMatrix(
    viewSpaceTransform,
    cameraTransform.Inverted)

  return Function.Transform(viewMatrix, worldPosition)

# -----------------------------------------------------------------------------

# Transforms a vector from view space to clip space.

def ViewToClip(
  viewPosition,
  aspectRatio,
  orthographicSize) :

  clipPosition = Function.Transform(
    CreateOrthoMatrix(aspectRatio, orthographicSize),
    Real4(viewPosition, 1.0))

  clipPosition /= clipPosition.X4

  return clipPosition.XYZ

# -----------------------------------------------------------------------------

# Transforms a vector from clip space to view space.

def ClipToView(
  clipPosition,
  aspectRatio,
  orthographicSize) :

  viewPosition = Function.Transform(
    CreateOrthoMatrix(aspectRatio, orthographicSize).Inverted,
    Real4(clipPosition, 1.0))

  viewPosition /= viewPosition.X4

  return viewPosition.XYZ

# -----------------------------------------------------------------------------

# Computes and applies the camera transform to fit all models into an image
# of given aspect ratio for the specified direction.

def AdjustCamera(
  camera,
  models,
  direction,
  aspectRatio) :

  # Adjust the camera towards the center of the combined bounding box of all
  # models to start with a proper rotation.
  bounds = AlignedBox.Empty
  for model in models :
    bounds |= model.Data.Mesh.Bounds

  camera.Adjust(direction, bounds)

  cameraTransform = camera.Transform.Matrix
  workspaceBasis = camera.Workspace.Basis

  # Compute the normalized clip space bounds of the projected models with
  # respect to the current camera matrix.
  min = Real2.MaxValue
  max = Real2.MinValue

  for model in models :
    mesh = model.Data.Mesh
    vertexCount = mesh.VertexCount
    for i in range(0, vertexCount) :
      wp = mesh.GetVertexPosition(i)
      vp = WorldToView(wp, cameraTransform, workspaceBasis)
      cp = ViewToClip(vp, aspectRatio, camera.OrthographicSize)
      min = minReal2(min, cp.XY)
      max = maxReal2(max, cp.XY)

  # Pan the camera such that it points to the center of the projected model.
  center = (max + min) / 2
  offsetClip = Real3(-center.X, center.Y, 0.5)
  offsetView = ClipToView(offsetClip, aspectRatio, camera.OrthographicSize)
  camera.Pan(offsetView.XY)

  # Scale the projection such that the entire range of the model fits in.
  r = compMaxReal2(max - min)
  camera.OrthographicSize = camera.OrthographicSize * r / 2.0

# -----------------------------------------------------------------------------

def CreateScreenshots() :

  # Capture some global references to manipulate the program state.
  app = Main.Application.Instance
  ui = app.UserInterface
  view = ui.PrimaryView
  project = app.ProjectManager.CurrentData

  # Query the user for the output directory.
  directories = ui.FolderBrowserDialog(
    title = "Select Directory",
    initialPath = String.Empty,
    multiSelect = False)

  # Check user input and cancel execution if no valid directory was specified.
  if directories.Length == 0 or String.IsNullOrEmpty(directories[0]) :
    return

  # Save view settings before changing.
  locator = project.Workspace.Locator.Visible
  basis = project.Workspace.Basis.Gizmo.Visible
  background1 = view.Background.TopLeftColor
  background2 = view.Background.TopRightColor
  background3 = view.Background.BottomLeftColor
  background4 = view.Background.BottomRightColor
  projection = view.Camera.ProjectionMode
  panning = view.Camera.PanningSpeed
  angle = view.Camera.Angle

  # Configure custom workspace view. Change to your liking.
  project.Workspace.Locator.Visible = False
  project.Workspace.Basis.Gizmo.Visible = False
  view.Background.TopLeftColor = Color.White
  view.Background.TopRightColor = Color.White
  view.Background.BottomLeftColor = Color.White
  view.Background.BottomRightColor = Color.White
  view.Camera.ProjectionMode = CameraProjectionMode.Orthographic
  view.Camera.PanningSpeed = 1.0

  # Configure render settings for each model.
  for model in project.Data.Models :
    model.Preview.GetSurfaceConfig(ViewMode.Sample).Tint = Color.Gray
    model.Preview.Selected = True

  # Create a texture buffer for rendering with the given target resolution.
  texture = app.DataProvider.CreateTexture2d(
    resolution = Natural2(2400, 1800),
    defaultValue = Color.Clear)

  # Compute the aspect ratio of the render target.
  aspectRatio = float(texture.Resolution.X) / texture.Resolution.Y

  # Iterate typical camera directions and render a screenshot for each one.
  for direction in Enum.GetValues(GetClrType(CameraViewDirection)) :
    AdjustCamera(view.Camera, project.Data.Models, direction, aspectRatio)
    view.RenderFrame(texture)
    filepath = System.IO.Path.Combine(directories[0], str(direction) + ".png")
    ui.SaveTexture(texture, filepath)

  # Restore view settings.
  project.Workspace.Locator.Visible = locator
  project.Workspace.Basis.Gizmo.Visible = basis
  view.Background.TopLeftColor = background1
  view.Background.TopRightColor = background2
  view.Background.BottomLeftColor = background3
  view.Background.BottomRightColor = background4
  view.Camera.ProjectionMode = projection
  view.Camera.PanningSpeed = panning
  view.Camera.Adjust(angle)

# -----------------------------------------------------------------------------

CreateScreenshots()

# -----------------------------------------------------------------------------