Rotate to North

可紊 提交于 2019-12-22 10:21:21

问题


After doing a complicated series of rotations and translations, I want to return the current direction to "North" pointing at the top of the page. How can I do that?

The obvious answer is to keep track of what direction I am pointing each time I translate, but that seems like a lot of work. I want something like "0 rotateto" that leaves me at the current location but pointing to the absolute top of the page; similarly, "90 rotateto" would point right.

I also want to know how to move to a specific point on the page after a series of rotations and translations. In other words, I'm looking for an "absolute moveto" instruction that moves to a specific point, not relative to the current translated coordinates.

My purpose is to implement a logo-style turtle. Two of the commands that a turtle recognizes are setpos (which moves to an absolute position) and setorientation (which rotates to an absolute direction). I'm trying to figure out how to implement those commands in PostScript.

EDIT 1:

Thanks luser droog, but something is still wrong. My library includes the turtle commands shown below with their PostScript equivalents; rotateto and setpos are the definitions you gave previously, is_penup is a global variable initially set to true, and I haven't yet considered pos and orientation, which report the current absolute x y position and orientation of the turtle:

init             -- %!
                    /rotateto { ... } bind def
                    /setpos { ... } def
                    newpath
                    306 396 moveto % center 8.5x11 portrait
                    0 setgray 2 setlinewidth
penup            -- % set global variable is_penup to true
pendown          -- % set global variable is_penup to false
forward n        -- if is_penup then 0 n rmoveto currentpoint translate
                                else 0 n rlineto currentpoint translate
backward n       -- if is_penup then 0 n neg rmoveto currentpoint translate
                                else 0 n neg rlineto currentpoint translate
right n          -- n neg rotate
left n           -- n rotate
setpos x y       -- x y setpos
setorientation n -- n rotateto
done             -- stroke newpage
pos              -- get current absolute x y position
orientation      -- get current absolute orientation

An example that draws two squares is shown below; the square command writes four lines of length 50, each followed by a 90 degree right turn:

init
pendown
setpos 100 100
square 50
setpos 400 400
right 45
square 25
done

But that doesn't work. Neither of the setpos commands is honored. There are two squares, and the second square is tilted at 45 degrees, but both begin at the center of the page. I'm afraid that each time I say currentpoint translate I interfere with what you are doing in your setpos command.

Can you offer any suggestions?

EDIT 2:

I decided not to worry about returning the current position and orientation to Scheme; that's too much work for my current purpose, though it may be useful sometime in the future. The final version of my Scheme code is shown below. Send is a common utility, turtle-init defines the turtle library in PostScript, the rest of the Scheme turtle library follows, then the sample program that draws two squares. It all works fine.

(define (send x . xs)
  (cond ((null? xs) (display x) (newline))
  (else (display x) (display " ") (apply send xs))))

(define (turtle-init)
  (for-each send '(
    "%!"
    "/defmat matrix defaultmatrix def"
    "/fix { currentpoint translate } def"
    "/rotateto { matrix rotate"
    "    dup 4 matrix currentmatrix 4 2 getinterval"
    "    putinterval setmatrix } def"
    "/setpos { defmat transform itransform moveto fix } def"
    "/left { rotate } def"
    "/right { neg rotate } def"
    "/is-pendown false def"
    "/penup { /is-pendown false def } def"
    "/pendown { /is-pendown true def } def"
    "/done { stroke showpage } def"
    "/init { initgraphics 306 396 moveto fix 0 setgray 2 setlinewidth } def"
    "/forward { 0 exch is-pendown { rlineto } { rmoveto } ifelse fix } def"
    "/backward { 0 exch is-pendown { rlineto } { rmoveto } ifelse fix } def")))
(define (turtle-penup) (send "penup"))
(define (turtle-pendown) (send "pendown"))
(define (turtle-forward n) (send n "forward"))
(define (turtle-backward n) (send n "backward"))
(define (turtle-right n) (send n "right"))
(define (turtle-left n) (send n "left"))
(define (turtle-setpos x y) (send x y "setpos"))
(define (turtle-setorientation n) (send n "rotateto"))
(define (turtle-done) (send "stroke showpage"))

(define (square n)
  (do ((i 4 (- i 1))) ((zero? i))
    (turtle-forward n) (turtle-right 90)))

(define (squares)
  (turtle-init)
  (turtle-pendown)
  (turtle-setpos 100 100)
  (square 50)
  (turtle-setpos 400 400)
  (turtle-right 45)
  (square 25)
  (turtle-done))

(with-output-to-file "squares.ps"
  (lambda () (squares)))

This will all appear at my blog some time in January; I'm not yet sure of the exact date.

Thanks!

EDIT 3:

I went back and read the documentation for the Logo turtle, and discovered that setpos honors the current state of the pen; thus, if the pen is down, setpos writes a line from the old position to the new position. I changed setpos to have the correct behavior:

/setpos { defmat transform itransform is-pendown
    { lineto } { moveto } ifelse fix } def

I also changed turtle-init so that it actually calls init instead of merely defining it. Not much use if you never call it.


回答1:


Well, to start with, the y-axis points north by default. So any normal translations and rotations can be reset with matrix defaultmatrix setmatrix. You can reset the scaling and rotation without modifying the translation with something like matrix currentmatrix dup 0 matrix defaultmatrix 0 4 getinterval putinterval setmatrix.

If you can ignore scaling, you could do rotateto (assuming the current position is the origin of user space) like this:

/rotateto { % angle  rotateto  -
    matrix rotate % create a rotation matrix
    dup 4
    matrix currentmatrix 4 2 getinterval %get the current translation
    putinterval setmatrix % put translation into rot. matrix and install
} bind def

To set an absolute position, you'll need to do a few transformations.

Normally, any coordinates specified refer to user space. That means they are multiplied by the Current Transformation Matrix before taking effect. To set an "absolute" position means interpreting coordinates as refering to an "absolute space". The only such priviledged space is the Default Matrix.

/setpos { % x-abs y-abs  setpos  -
    matrix defaultmatrix transform % x' y'  %to device space
    itransform % x y  %back to current user space
    translate
} def

Edit: This is tricky stuff! I was able to get your test to work with this suite of procedures.

%!
/defmat matrix defaultmatrix def
/fix { currentpoint translate } def
/rotateto { matrix rotate
    dup 4 matrix currentmatrix 4 2 getinterval
    putinterval setmatrix } def
/setpos { defmat transform itransform moveto fix } def
/left { rotate } def
/right { neg rotate } def
/is-pendown false def
/penup { /is-pendown false def } def
/pendown { /is-pendown true def } def
/done { stroke showpage } def
/init { initgraphics 306 396 moveto fix 0 setgray 2 setlinewidth } def
/pos { matrix currentmatrix 4 2 getinterval {} forall } def
/forward { 0 exch is-pendown { rlineto } { rmoveto } ifelse fix } def
/backward { 0 exch is-pendown { rlineto } { rmoveto } ifelse fix } def

/square { 4 { dup forward 90 right } repeat pop } def
init
pendown
100 100 setpos
50 square
400 400 setpos
45 right
25 square
done

One thing that tripped me up was remembering to currentpoint translate after every move (including the initial move).

For orientation, you'll have to "interpret" the matrix. I'll need to do some pondering on this before making suggestions. For a head-start, remember that a rotation matrix looks like [ cosA sinA -sinA cosA 0 0 ].




回答2:


Months Later...

We're all idiots! There most certainly is an "absolute" coordinate system: Device Space! Of course the operators moveto and currentpoint interface with your program in CTM-relative coordinates, you can manually "undo" the transformation to "take an absolute position" and "redo" it later to convert to CTM-relative coords.

This example uses the OO-Turtle, and a macro-expansion suite to execute a Lindenmayer System with embedded transformations (Queen Anne's Lace).

And the magic lines are:

     % ...
     currentpoint transform   % save "absolute" position
     % ...
     itransform translate
     0 0 moveto     % return to saved position
     % ...

You can leave them on the stack, save them in a variable. As long as you haven't collapsed the matrix to where the point is no longer referable (a scaling factor of 0), it will still refer to the same position when itransformed relative to whatever matrix happens to be current.

I've left the old, tedious way present in the comments so the full horror of it may serve as warning to posterity.

%!
%linden.ps

%one Iteration of macro-expansion
% traverses an array, replacing tokens
% defined in the dictionary P,
% and constructs a new array
/I { % O  I  O'
    mark exch {
        P exch 2 copy known {
            get aload pop
        }{
            exch pop
        } ifelse
    } forall counttomark array astore exch pop cvx
} def
%Generate nth expansion of O
/G { % n  G  O'^n
    /O load exch
    { I } repeat
} def

% n x y  x  -
% moveto x y, generate nth expansion of O, stroke, grestore
/x { moveto gsave G exec stroke grestore } def


/Turtle <<
    /forward {
        dup Turtle /angle get cos mul
        exch Turtle /angle get sin mul
        2 copy
        Turtle /pen? get { lineto }{ moveto }ifelse
        translate
    }
    /angle 90
    /pen? false
    /right { Turtle /angle 2 copy get 4 3 roll sub put }
    /left { Turtle /angle 2 copy get 4 3 roll add put }
    >> def
/send { get exec } def
/ini { 300 400 2 copy moveto translate } def

<< %shortcuts
    /F { R Turtle /forward send }
    /+ { T Turtle /right send }
    /- { T Turtle /left send }
>> begin

ini

{ %branching stems
/V << % description of the Lindenmeyer System
    /#0 {F leaf}
    /#1 {F}

    /O { #0 } % Original
    /P << % Productions
        /#0 { #1 [ - #0 ] + #0 }
        /#1 { #1 #1 }
        >>
    /R 10
    /T 45
    /leaf { 0 0 R 2 mul 0 180 arc 0 0 moveto }
>> def
V begin
0 -300 translate
.5 .5 scale
Turtle /pen? true put

gsave
    %"Special" Definitions
    % The brackets in the procedure resulting
    % from the expansion of /O are not used to
    % construct arrays, but to save and reset
    % the position and angle of the Turtle.
    ([) {
        %matrix currentmatrix 4 2 getinterval aload pop
        currentpoint transform
        Turtle /angle get
    } def
    (]) {
        Turtle /angle 3 2 roll put
        %matrix currentmatrix % Tx Ty M
        %dup 4 5 4 roll put % Ty M
        %dup 5 4 3 roll put % M
        %setmatrix
        itransform translate
        0 0 moveto
    } def
    .5 .5 scale
    8 0 0 x % execute 8-level-deep expansion of /O in /V
grestore

end showpage
} exec



回答3:


PostScript's 'moveto' is an absolute moveto, not relative. In PostScript if you want a relative moveto you use rmoveto. That said, there is basicallly no diredct access to device space. PostScript has two spaces a theoretically infinitely fine user space, and device space, which is what actually gets printed and has the resolution of the output device. The Current Transformation Matr4ix (CTM) maps user space to device space.

You can't access device space directly, all PostScript operations take place through the CTM. Thus there is no way to address an absolute point in device space, and so there is no 'absolute moveto' in the sense you are asking for, all points in user space are transformed through the CTM to get to device space.

There is no 'direction' in the sense that the Logo turtle has one either, from any point you can move directly to any other point, no need to rotate at all.

If you do rotate the CTM then yes, it really is up to you to keep track of that. You can reset the CTM in a number of ways. You can gsave/grestore round the series of operations. Note that this will reset everything, including the current point and path. If you want to preserve the current point then 'gsave.....currentpoint grestore moveto' would do that.

As luser droog notes you can use defaultmatrix setmatrix, or calculate your own matrix and use setmatrix, eg 'currentmatrix invertmatrix concat setmatrix'.

But in general you only rotate the CTM for special effects such as shearing or drawing images and so on, and the normal PostScript technique is to gsave/grestore round such sequences.

Instead of keeping track of the CTM, you could instead keep track of the turtle's current direction and apply geometry to calculate the new position after the turtle moves a given distance in that direction, which might simplify your problem.




回答4:


Stealing Ken's idea, and with further insight into what you're trying to do (from your blog), Here's a pseudo-OO postscript turtle.

%!
/Turtle <<
    /forward {
        dup Turtle /angle get cos mul
        exch Turtle /angle get sin mul
        2 copy
        Turtle /pen? get { lineto }{ moveto }ifelse
        translate
    }
    /angle 90
    /pen? false
    /right { Turtle /angle 2 copy get 4 3 roll sub put }
    /left { Turtle /angle 2 copy get 4 3 roll add put }
    >> def
/send { get exec } def
/ini { 300 400 2 copy moveto translate } def

<< %shortcuts
    /F { 100 Turtle /forward send }
    /+ { 90 Turtle /right send }
    /- { 90 Turtle /left send }
>> begin

ini
Turtle /pen? true put
F + F + F + F
F + F + F + F
F + F + F + F
F + F + F + F
stroke %draws a four-square
showpage


来源:https://stackoverflow.com/questions/8549026/rotate-to-north

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!