
ViewManager subclass: #OpenGL3DViewer
  instanceVariableNames: 
    ' camera object running renderingPane '
  classVariableNames: ''
  poolDictionaries: ''   !


!OpenGL3DViewer class methods !
  
comment
    "
A 3D Viewer

In order to view any 3D objects, a simple 3D viewer is needed.

The 3D viewer is in an infinite loop sending a deferred event which causes message
displayFrame to execute. Deferred events permit pull-down menu processing to be
intermixed with the execution cycle and also provides other windows with the opportunity
to execute. This loop is in progress as long as instance variable 'running' is true.
Deactivating or closing the window turns it off. Activating or opening the window turns it
on. Method displayFrame first executes checkForKeyInput and then executes update.
Navigating around an object is a matter of hitting the appropriate keys. (See method
helpSelected for details.) This results in translations or rotations of the camera. The
camera is actually part of the OpenGL pane and retrieved when the viewer is opened.
An update message sent to the viewer causes the same message to be sent to its panes.
When an OpenGL pane executes the update message, it first renders the camera
(since the pane is responsible for the camera) and then it generates a #needsContents
event which causes the renderOn: method in the viewer to execute. This  renderOn:
method renders the object being viewed.

    "!
   
example1
    "OpenGL3DViewer example1"
    ^self new open! !



!OpenGL3DViewer methods !
 
activate
    running := true.
    self generateDisplayEvent.!
 
checkForKeyInput
    | actions keys |
    actions := Notifier isControlKeyDown
        ifTrue: [#(turnUp turnDown turnRight turnLeft tiltRight tiltLeft)]
        ifFalse: [#(moveForward moveBack moveRight moveLeft moveUp moveDown)].
    keys := #('UpKey' 'DownKey' 'RightKey' 'LeftKey' 'PageUpKey' 'PageDownKey')
        collect: [:symbol| VirtualKeyConstants at: symbol].
    actions with: keys do: [:action :key |
        (Notifier isKeyDown: key)
            ifTrue: [self perform: action]].!

closed
    running := false!
   
deactivate
    running := false.!
  
displayFrame
    "While the window is running, generate displayFrame events to poll for
    key input while still allowing other applications to run."
    running ifTrue: [
        self checkForKeyInput.
        self update.
        self generateDisplayEvent]!
  
examplesSubmenu
    | aMenu messages |
    messages := OrderedCollection new.
    #(example1 example2 example2) with: (Array with: Face with: Maze with: Cube) do: [:selector :class |
        messages add: (Message
            receiver: self
            selector: #reOpenOn:
            arguments: (Array with: (class perform: selector)))].
    aMenu := Menu
        labels: 'Example 1\Example 2\Example 3' withCrs
        lines: #()
        selectors: messages asArray.
    ^aMenu
            title: '&Examples';
            owner: self;
            yourself!
  
generateDisplayEvent
    self sendDeferredEvent: #displayFrame!

helpSelected
    MessageBox message: '
        Up arrow key: move forward
        Down arrow key: move backward
        Left arrow key: move left
        Right arrow key: move right
        Page Up key: move up
        Page Down: move down

        Ctrl + Up arrow key: look up
        Ctrl + Down arrow key: look down
        Ctrl + Left arrow key: look left
        Ctrl + Right arrow key: look right
        Ctrl + Page Up key: rotate clockwise
        Ctrl + Page Down: rotate counterclockwise             '!
  
initialize
    super initialize.
    running := false.
    object := Face new.!
  
moveBack
    camera translateBy: #(0.0 0.0 1.0)!
   
moveDown
    camera translateBy: #(0.0 -1.0 0.0)!
  
moveForward
    camera translateBy: #(0.0 0.0 -1.0)!
   
moveLeft
    camera translateBy: #(-1.0 0.0 0.0)!
  
moveRight
    camera translateBy: #(1.0 0.0 0.0)!
  
moveUp
    camera translateBy: #(0.0 1.0 0.0)!
 
open
    | aPane |
    self labelWithoutPrefix: 'OpenGL Window'.
    aPane := (OpenGLPane new
            noScrollBars;
            framingBlock: [:aBox | aBox ];
            addClipchildrenStyle;
            addClipsiblingsStyle;
            yourself).
    self addSubpane: aPane.
    aPane
        when: #needsContents send: #renderOn: to: self with: aPane;
        when: #needsMenu send: #openGLMenu: to: self with: aPane.
    self mainView
        when: #closed send: #closed to: self;
        when: #activated send: #activate to: self;
        when: #deactivated send: #deactivate to: self.
    camera := aPane camera.
    renderingPane := aPane.
    self openWindow.!

openGLMenu: aPane
    aPane setMenu: ((Menu new
        appendItem: 'Help' selector: #helpSelected;
        appendSeparator;
        appendSubMenu: self examplesSubmenu)
            title: '&OpenGL';
            owner: self;
            yourself)!
  
openOn: a3DObject
    object := a3DObject.
    self open.!

renderOn: aPane
    "Display the windows contents on the openGL Pane."
    object basicRenderOn: aPane!
   
reOpenOn: a3DObject
    | aPane |
    renderingPane graphicsTool reinitialize.
    camera initialize.
    object := a3DObject.
    self update!

sendDeferredEvent: selector
        "Private - Add a Message to DeferredRequests."
    ( Message new
        receiver: self
        selector: selector
        arguments: Array new ) performDeferred.
    OSEventSemaphore signal!
   
tiltLeft
    camera rotateBy: 10.0 around: #(0.0 0.0 1.0)!
 
tiltRight
    camera rotateBy: -10.0 around: #(0.0 0.0 1.0)!
   
turnDown
    camera rotateBy: -10.0 around: #(1.0 0.0 0.0).!
   
turnLeft
    camera rotateBy: 10.0 around: #(0.0 1.0 0.0)!
 
turnRight
    camera rotateBy: -10.0 around: #(0.0 1.0 0.0)!
   
turnUp
    camera rotateBy: 10.0 around: #(1.0 0.0 0.0).!
  
update
    self mainView children do: [:pane|
        pane update].! !
