-- File: SVViewerInputImpl.mesa
-- Last edited by Bier on December 18, 1982 1:48 am
-- Author: Eric Bier on July 6, 1983 3:22 pm
-- Contents: Procedures for responding to button clicks made in a solidviewer

DIRECTORY
 CastRays,
 CoordSys,
CSG,
 CSGGraphics,
 DisplayList3d,
 Graphics,
 GraphicsColor,
 Matrix3d,
 Preprocess3d,
 SV2d,
 SVBoundBox,
 SVEditUser,
 SVInputMonitor,
 SVVector3d,
 SVViewerInput,
 SVViewerUser,
 TIPUser,
 ViewerClasses;

SVViewerInputImpl: PROGRAM
IMPORTS CastRays, CoordSys, CSGGraphics, DisplayList3d, Graphics, Matrix3d, Preprocess3d, SVBoundBox, SVEditUser, SVInputMonitor, SVViewerUser, SVVector3d
EXPORTS SVViewerInput =

BEGIN
Assembly: TYPE = DisplayList3d.Assembly;
BoundBox: TYPE = SVBoundBox.BoundBox;
Camera: TYPE = CSGGraphics.Camera;
Classification: TYPE = CSG.Classification;
Color: TYPE = GraphicsColor.Color;
CoordSystem: TYPE = CoordSys.CoordSystem;
FrameBox: TYPE = CSGGraphics.FrameBox;
Matrix4by4: TYPE = Matrix3d.Matrix4by4;
Point2d: TYPE = SV2d.Point2d;
Point3d: TYPE = Matrix3d.Point3d;
Primitive: TYPE = CSG.Primitive;
Selection: TYPE = SVEditUser.Selection;
Vector: TYPE = SVVector3d.Vector;
ViewerToolData: TYPE = SVViewerUser.ViewerToolData;
ViewerPictureData: TYPE = SVViewerUser.ViewerPictureData;

InputNotify: PUBLIC SAFE PROCEDURE [self: ViewerClasses.Viewer, input: LIST OF REF ANY] = TRUSTED {
-- self is a viewer picture
IF ISTYPE[input.first, TIPUser.TIPScreenCoords] THEN {
 controlPoint: Point2d;
 viewerPictureData: ViewerPictureData ← NARROW[self.data];
 viewerToolData: ViewerToolData ← viewerPictureData.viewerToolData;
 camera: Camera ← viewerPictureData.camera;
 mousePlace: TIPUser.TIPScreenCoords ← NARROW[input.first];
 controlPoint[1] ← mousePlace.mouseX; -- this is a fix to float conversion
 controlPoint[2] ← mousePlace.mouseY;
 controlPoint ← CoordSys.ScreenToCamera[controlPoint, camera.screenCS];
  -- convert to camera coordinates
IF ISTYPE[input.rest.first, ATOM] THEN
  SELECT input.rest.first FROM
  $SingleRay => {
  SVViewerUser.SingleRay[viewerToolData, controlPoint];
   };
  $FrameUpLeft => PositionUpLeft[viewerToolData, controlPoint];
  $FrameDownRightMove => {
   -- PositionDownRight[viewerToolData, controlPoint];
    };
  $FrameDownRightEnd => {
  PositionDownRight[viewerToolData, controlPoint];
   };
  $DeleteFrame => DeleteFrame[viewerToolData];
  $StartPrimarySelection => StartPrimarySelection[viewerToolData, controlPoint];
  $StartSecondarySelection => StartSecondarySelection[viewerToolData, controlPoint];
  $SetPrimarySelection => SVInputMonitor.NewMotion[controlPoint, NARROW[input.rest.first], viewerToolData];
  $EndPrimarySelection => {};
  $SetSecondarySelection => SVInputMonitor.NewMotion[controlPoint, NARROW[input.rest.first], viewerToolData];
  $EndSecondarySelection => EndSecondarySelection[viewerToolData, controlPoint];
  ENDCASE;
 }; -- is a mousepoint
}; -- end of InputNotify

MakeAlignedMat: PRIVATE PROC [worldNormal: Vector, surfacePtInWorld: Point3d, cs: CoordSystem] RETURNS [mat: Matrix4by4] = TRUSTED {
-- create a Matrix4by4 with origin at surfacePtInWorld whose z axis is parallel to worldNormal and whose x zxis is orthogonal to both worldNormal and the y axis of cs in WORLD coordinates. Assume that cs.wrtWorld is accurate
 yAxisOfCS: Vector ← Matrix3d.YAxisOfMatrix[cs.wrtWorld];
 xAxis: Vector;
IF SVVector3d.Parallel[yAxisOfCS, worldNormal] THEN xAxis ← Matrix3d.XAxisOfMatrix[cs.wrtWorld]
ELSE xAxis ← SVVector3d.CrossProduct[yAxisOfCS, worldNormal];
 mat ← Matrix3d.MakeMatFromZandXAxis[worldNormal, xAxis, surfacePtInWorld];
 };

StartPrimarySelection: PUBLIC PROC [viewerToolData: ViewerToolData, cameraPoint: Point2d] = TRUSTED {
-- compute the ray tracing tree for the current scene here so we don't have to recompute it each time the mouse position is sampled (SetPrimarySelection).
 selectionMat: Matrix4by4;
 surfacePtInWorld: Point3d;
 worldNormal: Vector;
 assembly: Assembly;
 primitive: Primitive;
 class: Classification;
 selection, oldSelection: Selection;
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 viewerPictureData.tree ← DisplayList3d.SceneToTree[viewerPictureData.scene, viewerPictureData.camera];
 [] ← Preprocess3d.Preprocess[viewerPictureData.tree, viewerPictureData.camera];
 class ← CastRays.SingleRay2[cameraPoint, viewerPictureData.tree, viewerPictureData.scene.lightSources, viewerPictureData.camera];
 [surfacePtInWorld, worldNormal] ← DisectClassification1[class, viewerPictureData.camera, cameraPoint];
 [assembly, primitive] ← DisectClassification2[class, viewerPictureData.camera, cameraPoint];
IF assembly # NIL THEN {
  selectionMat ← MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys];
  oldSelection ← SVEditUser.GetPrimarySelection[];
  ComplementOldSelection[viewerToolData, oldSelection, GraphicsColor.black];
  selection ← SVEditUser.SetPrimarySelection[cameraPoint, assembly, primitive, selectionMat, viewerToolData];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- draw new selection into complement mode
  };
 CastRays.ReturnClassToPool[class];
};

ComplementOldSelection: PROC [viewerToolData: ViewerToolData, selection: Selection, color: Color] = TRUSTED {
-- if selection is in this viewer then just complement it normally. If it is in another viewer, then have that viewer complement it. If there it is a null selection, forget it.
IF selection = NIL THEN RETURN; -- null selection
IF selection.assembly = NIL THEN RETURN; -- null selection
IF selection.viewerToolData = viewerToolData
  THEN ComplementSelection[viewerToolData, selection, color]
ELSE ComplementSelection[selection.viewerToolData, selection, color];
 };


DrawSelectionOpaque: PROC [viewerToolData: ViewerToolData, selection: Selection, color: Color] = TRUSTED {
DoDrawSelectionOpaque: SAFE PROC [dc: Graphics.Context] = TRUSTED {
  [] ← Graphics.SetPaintMode[dc, opaque];
  Graphics.SetColor[dc, color];
  SVViewerUser.DrawOneCS[dc, viewerToolData, selection.coordSys];
  };
 CoordSys.TellAboutParent[selection.coordSys];
 SVViewerUser.Painter[DoDrawSelectionOpaque, viewerToolData];
 };

ComplementSelection: PROC [viewerToolData: ViewerToolData, selection: Selection, color: Color] = TRUSTED {
DoComplementSelection: SAFE PROC [dc: Graphics.Context] = TRUSTED {
  [] ← Graphics.SetPaintMode[dc, invert];
  Graphics.SetColor[dc, color];
  SVViewerUser.DrawOneCS[dc, viewerToolData, selection.coordSys];
  };
 CoordSys.TellAboutParent[selection.coordSys];
 SVViewerUser.Painter[DoComplementSelection, viewerToolData];
 };

SetPrimarySelection: PUBLIC PROC [viewerToolData: ViewerToolData, cameraPoint: Point2d] = TRUSTED {
-- cast a ray into the scene from this point. If it hits anything, use the surface normal at that point to derive a coordinate system whose z axis is the parallel to the normal. Tell the editTool that a new selection has been made.
 selection: Selection;
 surfacePtInWorld: Point3d;
 worldNormal: Vector;
 assembly: Assembly;
 primitive: Primitive;
 selectionMat: Matrix4by4;
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 class: Classification ←
  CastRays.SingleRay2[cameraPoint, viewerPictureData.tree,
        viewerPictureData.scene.lightSources, viewerPictureData.camera];
 [surfacePtInWorld, worldNormal] ←
  DisectClassification1[class, viewerPictureData.camera, cameraPoint];
 [assembly, primitive] ← DisectClassification2[class, viewerPictureData.camera, cameraPoint];
IF assembly # NIL THEN {
  selectionMat ← MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys];
  selection ← SVEditUser.GetPrimarySelection[];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- erase old one by complementing
  selection ← SVEditUser.SetPrimarySelection[cameraPoint, assembly, primitive, selectionMat, viewerToolData];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- paint new one by complementing
  };
 CastRays.ReturnClassToPool[class];
 };

-- There are two Disect Classifications because Cedar can't handle a return record which both contains a pointer (assembly) and returns 6 REALS (long pointer-containing return record is unsafe)

DisectClassification1: PROC [class: Classification, camera: Camera, cameraPoint: Point2d] RETURNS [surfacePtInWorld: Point3d, worldNormal: Vector] = TRUSTED {
 worldBasePt: Point3d;
 primitive: Primitive;
 primitiveNormal, worldDirection: Vector;
 t: REAL;
IF class.count = 0 THEN RETURN;
 t ← class.params[1]; -- the parameter of the ray intersection
 primitiveNormal ← class.normals[1];
 primitive ← class.primitives[1];
 worldNormal ← Matrix3d.UpdateVectorWithInverse[primitive.worldWRTPrim, primitiveNormal];
-- express the ray which was cast in world coordinates
 worldBasePt ← Matrix3d.Update[camera.coordSys.mat,
  [cameraPoint[1], cameraPoint[2], 0]];
 worldDirection ← Matrix3d.UpdateVectorEvenScaling[camera.coordSys.mat, [cameraPoint[1], cameraPoint[2], -camera.focalLength]];
-- use this ray to find the surface point in WORLD coordinates. This will be the origin of our coordinate system.
 surfacePtInWorld[1] ← worldBasePt[1] + t*worldDirection[1];
 surfacePtInWorld[2] ← worldBasePt[2] + t*worldDirection[2];
 surfacePtInWorld[3] ← worldBasePt[3] + t*worldDirection[3];
 }; -- end of DisectClassification1

DisectClassification2: PROC [class: Classification, camera: Camera, cameraPoint: Point2d] RETURNS [assembly: Assembly, primitive: Primitive] = TRUSTED {
IF class.count = 0 THEN RETURN[NIL, NIL];
 primitive ← class.primitives[1];
 assembly ← NARROW[primitive.assembly];
 }; -- end of DisectClassification2

StartSecondarySelection: PUBLIC PROC [viewerToolData: ViewerToolData, cameraPoint: Point2d] = TRUSTED {
-- compute the ray tracing tree for the current scene here so we don't have to recompute it each time the mouse position is sampled (SetSecondarySelection).
 selectionMat: Matrix4by4;
 surfacePtInWorld: Point3d;
 worldNormal: Vector;
 assembly: Assembly;
 primitive: Primitive;
 class: Classification;
 selection, oldSelection: Selection;
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 viewerPictureData.tree ← DisplayList3d.SceneToTree[viewerPictureData.scene, viewerPictureData.camera];
 [] ← Preprocess3d.Preprocess[viewerPictureData.tree, viewerPictureData.camera];
 class ← CastRays.SingleRay2[cameraPoint, viewerPictureData.tree, viewerPictureData.scene.lightSources, viewerPictureData.camera];
 [surfacePtInWorld, worldNormal] ← DisectClassification1[class, viewerPictureData.camera, cameraPoint];
 [assembly, primitive] ← DisectClassification2[class, viewerPictureData.camera, cameraPoint];
IF assembly # NIL THEN {
  selectionMat ← MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys];
  oldSelection ← SVEditUser.GetSecondarySelection[];
  ComplementOldSelection[viewerToolData, oldSelection, GraphicsColor.black];
  selection ← SVEditUser.SetSecondarySelection[cameraPoint, assembly, primitive, selectionMat, viewerToolData];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- draw new selection into complement mode
  };
 CastRays.ReturnClassToPool[class];
};


SetSecondarySelection: PUBLIC PROC [viewerToolData: ViewerToolData, cameraPoint: Point2d] = TRUSTED {-- cast a ray into the scene from this point. If it hits anything, use the surface normal at that point to derive a coordinate system whose z axis is the parallel to the normal. Tell the editTool that a new selection has been made.
 selection: Selection;
 surfacePtInWorld: Point3d;
 worldNormal: Vector;
 assembly: Assembly;
 primitive: Primitive;
 selectionMat: Matrix4by4;
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 class: Classification ←
  CastRays.SingleRay2[cameraPoint, viewerPictureData.tree,
        viewerPictureData.scene.lightSources, viewerPictureData.camera];
 [surfacePtInWorld, worldNormal] ←
  DisectClassification1[class, viewerPictureData.camera, cameraPoint];
 [assembly, primitive] ← DisectClassification2[class, viewerPictureData.camera, cameraPoint];
IF assembly#NIL THEN {
  selectionMat ← MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys];
  selection ← SVEditUser.GetSecondarySelection[];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- erase old one by complementing
  selection ← SVEditUser.SetSecondarySelection[cameraPoint, assembly, primitive, selectionMat, viewerToolData];
  ComplementSelection[viewerToolData, selection, GraphicsColor.black]; -- paint new one by complementing
 -- fake a button press to draw scene
  };
 CastRays.ReturnClassToPool[class];
 };

EndSecondarySelection: PUBLIC PROC [viewerToolData: ViewerToolData, controlPoint: Point2d] = TRUSTED {
 oldSelection, secondarySelection: Selection;
 oldSelection ← SVEditUser.GetFinalSecondary[];
 SetSecondarySelection[viewerToolData, controlPoint]; -- set the selection to the final point
 secondarySelection ← SVEditUser.GetSecondarySelection[];
 [] ← SVEditUser.SetFinalSecondary[secondarySelection.cameraPoint, secondarySelection.assembly, secondarySelection.primitive, secondarySelection.coordSys.wrtWorld, secondarySelection.viewerToolData];
-- is there an old selection in this viewer to complement?
IF oldSelection # NIL THEN {
  IF oldSelection.viewerToolData = viewerToolData AND oldSelection.assembly # NIL
   THEN ComplementBoundBox[oldSelection.primitive.boundBox, viewerToolData];
  };
IF secondarySelection.assembly # NIL THEN
  ComplementBoundBox[secondarySelection.primitive.boundBox, viewerToolData];
 };

ComplementBoundBox: PUBLIC PROC [boundBox: BoundBox, viewerToolData: ViewerToolData] = TRUSTED {
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 camera: Camera ← viewerPictureData.camera;
 screen: CoordSystem ← camera.screenCS;
DoComplementBoundBox: SAFE PROC [dc: Graphics.Context] = TRUSTED {
  SVBoundBox.ComplementBoundBox[dc, boundBox, screen];
  };
 SVViewerUser.Painter[DoComplementBoundBox, viewerToolData];
 };

PositionUpLeft: PUBLIC PROC [viewerToolData: ViewerToolData, controlPoint: Point2d] = TRUSTED {
-- controlPoint is in camera coordinates
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 camera: Camera ← viewerPictureData.camera;
 frame: FrameBox ← camera.frame;
 frame.fullScreen ← FALSE;
 frame.downLeft[1] ← controlPoint[1];
 frame.upRight[2] ← controlPoint[2];
 };

PositionDownRight: PUBLIC PROC [viewerToolData: ViewerToolData, controlPoint: Point2d] = TRUSTED {
-- controlPoint is in camera coordinates
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 camera: Camera ← viewerPictureData.camera;
 frame: FrameBox ← camera.frame;
 frame.downLeft[2] ← controlPoint[2];
 frame.upRight[1] ← controlPoint[1];
 DrawCameraFrame[viewerToolData];
 };

DeleteFrame: PROC [viewerToolData: ViewerToolData] = TRUSTED {
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 camera: Camera ← viewerPictureData.camera;
 frame: FrameBox ← camera.frame;
 frame.fullScreen ← TRUE;
 };

DrawCameraFrame: PROC [viewerToolData: ViewerToolData] = TRUSTED {
 viewerPictureData: ViewerPictureData ← NARROW[viewerToolData.viewerPicture.data];
 camera: Camera ← viewerPictureData.camera;
DoDrawFrame: SAFE PROC [dc: Graphics.Context] = TRUSTED {
  Graphics.SetColor[dc, GraphicsColor.black];
   CSGGraphics.DrawFrame [dc, camera];
   };
 SVViewerUser.Painter[DoDrawFrame, viewerToolData];
};

END.