-- ChipOrientImpl.mesa

-- A package to transform Chipmonk rectangles and
-- orientations.

-- last edited by E. McCreight, December 15, 1982  6:10 PM
-- written by E. McCreight, February 22, 1982  10:11 AM

DIRECTORY
  ChipOrient,
  InlineDefs,
  ppdefs;

ChipOrientImpl: PROGRAM
  IMPORTS ChipOrient, InlineDefs, ppdefs
  EXPORTS ChipOrient =
  BEGIN OPEN ppdefs, ChipOrient;


  -- Orientation Transformations

  -- x increases to the right, y increases downward.

  -- An orientationIndex idx is composed of two parts:
  --    a clockwise rotation of (idx/2)*45 degrees,
  --    followed by a reflection in x if (idx MOD 2)#0.
  -- Odd multiples of 45 degrees are not yet supported.

  MapRect: PUBLIC PROC [itemInCell: Rect, cellSize: Point,
    cellInstOrient: orientationIndex, cellInstPos: Point ← [0,0]]
    RETURNS [itemInWorld: Rect] =

    -- Given an item in a prototype cell, and the
    -- size of the prototype cell, both in "cell" co-ordinates, and
    -- the position and orientation index of an instance of that cell in "world"
    -- co-ordinates, this procedure returns the world
    -- co-ordinates of the instance of the item.

    BEGIN OPEN InlineDefs;
    x1, y1, x2, y2, sizeX, sizeY, t: locNum;
    IF cellInstOrient=0 THEN
      RETURN[[x1: cellInstPos.x+itemInCell.x1,
        x2: cellInstPos.x+itemInCell.x2,
        y1: cellInstPos.y+itemInCell.y1,
        y2: cellInstPos.y+itemInCell.y2]];
        -- most common case
    [x: sizeX, y: sizeY] ← cellSize;
    [x1: x1, x2: x2, y1: y1, y2: y2] ← itemInCell;

    SELECT BITAND[cellInstOrient, 14B] FROM
      0 => NULL;
      4 => -- 90 degrees clockwise
        BEGIN
        t ← y1;
        y1 ← x1;
        x1 ← sizeY-y2;
        y2 ← x2;
        x2 ← sizeY-t;
        sizeX ← sizeY;
        END;
      10B => -- 180 degrees clockwise
        BEGIN
        t ← sizeX-x1; x1 ← sizeX-x2; x2 ← t;
        t ← sizeY-y1; y1 ← sizeY-y2; y2 ← t;
        END;
      14B => -- 270 degrees clockwise
        BEGIN
        t ← x1;
        x1 ← y1;
        y1 ← sizeX-x2;
        x2 ← y2;
        y2 ← sizeX-t;
        sizeX ← sizeY;
        END;
      ENDCASE;

    IF BITAND[cellInstOrient, 1]#0 THEN -- mirror in x
      {t ← sizeX-x1; x1 ← sizeX-x2; x2 ← t};

    itemInWorld ← [x1: cellInstPos.x+x1, -- translate by cellInstPos
      y1: cellInstPos.y+y1, x2: cellInstPos.x+x2,
      y2: cellInstPos.y+y2];
    END;


  DeMapRect: PUBLIC PROC [itemInWorld: Rect, cellSize: Point,
    cellInstOrient: orientationIndex, cellInstPos: Point ← [0,0]]
    RETURNS [itemInCell: Rect] =
    BEGIN OPEN InlineDefs;

    -- Given an item in world co-ordinates, and the
    -- size of a prototype cell, both in "cell" co-ordinates, and
    -- the position and orientation index of an instance of that cell in "world"
    -- co-ordinates, this procedure returns the cell prototype
    -- co-ordinates of that item.

    inverseOrient: orientationIndex = InverseOrient[cellInstOrient];
    itemInCell ← MapRect[
      itemInCell: [x1: itemInWorld.x1-cellInstPos.x, y1: itemInWorld.y1-cellInstPos.y,
            x2: itemInWorld.x2-cellInstPos.x, y2: itemInWorld.y2-cellInstPos.y],
      cellInstOrient: inverseOrient,
      cellSize: Size[size: cellSize, orient: inverseOrient]];
    END;


  ComposeOrient: PUBLIC PROC [
    itemOrientInCell, cellOrientInWorld: orientationIndex]
    RETURNS [itemOrientInWorld: orientationIndex] =
    BEGIN OPEN InlineDefs;

    -- This procedure produces the composite orientation
    -- obtained by first doing itemOrientInCell and then
    -- doing cellOrientInWorld.  It uses the observation
    -- that a reflection in x followed by a z-degree clockwise
    -- rotation is the same as a (360-z)-degree clockwise rotation
    -- followed by a reflection in x.  Thus if itemOrientInCell
    -- contains a final reflection, then cellOrientInWorld's rotation
    -- operates in reverse.

    refl1: [0..1] ← BITAND[itemOrientInCell, 1];
    rot2: [0..14] ← BITAND[cellOrientInWorld, 14];
    IF refl1#0 AND rot2#0 THEN rot2 ← 16-rot2;
    itemOrientInWorld ← BITAND[BITAND[itemOrientInCell, 14]+
      rot2+BITXOR[refl1, BITAND[cellOrientInWorld, 1]], 15];
    END;


  InverseOrient: PUBLIC PROC [orient: orientationIndex]
    RETURNS [inverse: orientationIndex] =
    BEGIN OPEN InlineDefs;

    -- For all orientationIndexes o1, ComposeOrient[o1, InverseOrient[o1]] = 0.

    -- A reflection in x followed by a z-degree clockwise
    -- rotation is the same as a (360-z)-degree clockwise rotation
    -- followed by a reflection in x.  Thus if orient
    -- contains a final reflection, then the inverse rotation
    -- operates in the same direction as the forward one.

    refl: [0..1] ← BITAND[orient, 1];
    rot: [0..14] ← BITAND[orient, 14];
    IF refl=0 AND rot#0 THEN rot ← 16-rot;
    inverse ← rot+refl;
    END;


  DecomposeOrient: PUBLIC PROC [
    itemOrientInWorld, cellOrientInWorld: orientationIndex]
    RETURNS [itemOrientInCell: orientationIndex] =
    BEGIN

    -- For all orientationIndexes o1 and o2,
    -- DecomposeOrient[cellOrientInWorld: o1, itemOrientInWorld:
    --   ComposeOrient[cellOrientInWorld: o1, itemOrientInCell: o2]] = o2.

    RETURN[ComposeOrient[itemOrientInCell: itemOrientInWorld,
      cellOrientInWorld: InverseOrient[cellOrientInWorld]]];
    END;


  RefPt: PUBLIC PROC [r: Rect] RETURNS [Point] =
    {RETURN[[x: ((r.x1+r.x2)/(2*Lambda))*Lambda,
      y: ((r.y1+r.y2)/(2*Lambda))*Lambda]]};


  NormalizeList: PUBLIC PROC [lp: listPtr,
    origin: Point ← [0,0]] RETURNS [size, offset: Point] =
    BEGIN
    r: Rect ← BoundingRect[lp];
    offset ← [x: origin.x-r.x1, y: origin.y-r.y1];
    size ← [x: r.x2-r.x1, y: r.y2-r.y1];
    FOR lpp: listPtr ← lp, lpp.nxt WHILE lpp#NIL DO
      lpp.lx ← lpp.lx+offset.x;
      lpp.ly ← lpp.ly+offset.y;
      ENDLOOP;
    END;


  BoundingRect: PUBLIC PROC [lp: listPtr]
    RETURNS [r: Rect] =
    BEGIN
    x1, y1, x2, y2: locNum;
    [mix: x1, miy: y1, max: x2, may: y2] ← minmax[lp];
    r ← [x1: x1, y1: y1, x2: x2, y2: y2];
    END;


  DecomposeRect: PUBLIC PROC [r, test: Rect, rInsideTest, rOutsideTest: PROC[Rect]] =
    BEGIN
    IF r.x1<test.x1 THEN
      BEGIN
      rOutsideTest[[x1: r.x1, y1: r.y1, x2: MIN[r.x2, test.x1], y2: r.y2]];
      IF r.x2<test.x1 THEN RETURN;
      END;
    IF r.y1<test.y1 THEN
      BEGIN
      rOutsideTest[[x1: MAX[test.x1, r.x1], y1: r.y1, x2: r.x2, y2: MIN[r.y2, test.y1]]];
      IF r.y2<test.y1 THEN RETURN;
      END;
    IF test.y2<r.y2 THEN
      BEGIN
      rOutsideTest[[x1: MAX[test.x1, r.x1], y1: MAX[test.y2, r.y1], x2: r.x2,
        y2: r.y2]];
      IF test.y2<r.y1 THEN RETURN;
      END;
    IF test.x2<r.x2 THEN
      BEGIN
      rOutsideTest[[x1: MAX[test.x2, r.x1], y1: MAX[test.y1, r.y1], x2: r.x2,
        y2: MIN[r.y2, test.y2]]];
      IF test.x2<r.x1 THEN RETURN;
      END;
    rInsideTest[[x1: MAX[test.x1, r.x1], y1: MAX[test.y1, r.y1],
      x2: MIN[test.x2, r.x2], y2: MIN[test.y2, r.y2]]];
    END;


  -- Certification tests, can be commented out for speed of startup

--  FOR o1: orientationIndex IN orientationIndex DO
--    IF ComposeOrient[o1, InverseOrient[o1]] # 0 THEN ERROR;
--    FOR o2: orientationIndex IN orientationIndex DO
--      IF DecomposeOrient[cellOrientInWorld: o1, itemOrientInWorld:
--        ComposeOrient[cellOrientInWorld: o1, itemOrientInCell: o2]] # o2 THEN ERROR;
--      ENDLOOP;
--    ENDLOOP;

  END. -- of ChipOrientImpl