;;; Example openmcl FFI by hamlink
;;;
;;; 2d Gasket example taken from
;;;  "Interactive Computer Graphics:
;;;   A Top-Down Approach with OpenGL" by Ed Angel

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ccl:use-interface-dir :GL))

(defpackage "2DGASKET")
(defpackage "OPENGL"
    (:nicknames :opengl :gl)
    (:export "INITIALIZE-GLUT"
	     "WITH-MATRIX-MODE"))

;;; Opening "libglut.so" should also open "libGL.so", "libGLU.so",
;;; and other libraries that they depend on.
;;; It seems that it does on some platforms and not on others;
;;; explicitly open what we require here.
(eval-when (:compile-toplevel :load-toplevel :execute)
  #+linux-target
  (dolist (lib '("libGL.so" "libGLU.so" "libglut.so"))
    (open-shared-library lib))
  #+darwin-target
  (let* ((s (make-semaphore)))
    (process-interrupt ccl::*initial-process*
		       (lambda ()
			 (open-shared-library "GLUT.framework/GLUT")
			 (signal-semaphore s)))
    (wait-on-semaphore s))
  )

(in-package :opengl)

;; glut complains if it's initialized redundantly
(let ((glut-initialized-p nil))
  (defun initialize-glut ()
    (let ((command-line-strings (list "ccl")))
      (when (not glut-initialized-p)
        (ccl::with-string-vector (argv command-line-strings)
          (rlet ((argcp :int (length command-line-strings)))
	    (#_glutInit argcp argv)))
	(setf glut-initialized-p t))))
  ;; When a saved image is restarted, it needs to know that glut
  ;; hasn't been initialized yet.
  (defun uninitialize-glut ()
    (setf glut-initialized-p nil))
  )

(pushnew #'uninitialize-glut ccl::*save-exit-functions*
	 :key #'ccl::function-name)

(defparameter *matrix-mode* #$GL_MODELVIEW)
(defmacro with-matrix-mode (mode &body body)
  `(unwind-protect
       (let ((*matrix-mode* ,mode))
	 (#_glMatrixMode *matrix-mode*)
	 ,@body)
     (#_glMatrixMode *matrix-mode*)))

(in-package :2dgasket)

(defun myinit ()
  (#_glClearColor 1.0 1.0 1.0 0.0) ; white background
  (#_glColor3f 1.0 0.0 0.0) ; red pen color

  (opengl:with-matrix-mode #$GL_PROJECTION
    (#_glLoadIdentity)
    (#_gluOrtho2D 0.0D0 500.0D0 0.0D0 500.0D0))

  ; (#_glEnable #$GL_DEPTH_TEST) ; for 3d only

  (#_srand (#_time (%null-ptr)))
  )

;; 2d gasket using points

(ccl::defcallback display-cb (:void)
  (let ((bounds #2a((0.0 0.0) (250.0 500.0) (500.0 0.0)))
	(point #(75.0 50.0)))
    (#_glClear #$GL_COLOR_BUFFER_BIT)
    (#_glBegin #$GL_POINTS)
    (dotimes (i 5000)
      (let ((j (random 3)))
	(setf (aref point 0) (/ (+ (aref point 0) (aref bounds j 0)) 2.0)
	      (aref point 1) (/ (+ (aref point 1) (aref bounds j 1)) 2.0))
	(#_glVertex2f (aref point 0) (aref point 1))))
    (#_glEnd)
    (#_glFlush)))

(defun main () ; no int argc or char **argv
  (opengl:initialize-glut)
  (#_glutInitDisplayMode (logior #$GLUT_RGB
				 #$GLUT_SINGLE
				 #+ignore #$GLUT_DEPTH))
  (#_glutInitWindowSize 500 500)
  (#_glutInitWindowPosition 0 0)
  (ccl::with-cstrs ((title "simple OpenGL example"))
    (#_glutCreateWindow title))
  (#_glutDisplayFunc display-cb)
  (myinit)

  ;; It appears that glut provides no mechanism for doing the event loop
  ;; yourself -- if you want to do that, you should use some other set of
  ;; libraries and make your own GUI toolkit.
  
  (#_glutMainLoop) ; this never returns
  )


;;; With native threads, #_glutMainLoop doesn't necessarily interfere
;;; with scheduling: we can just run all of the OpenGL code in a
;;; separate thread (which'll probably spend most of its time blocked
;;; in GLUT's event loop.)  On OSX (especially) it may work best to
;;; force the GLUT event loop to run on the main thread, which
;;; ordinarily does period "housekeeping" tasks.  Start another thread
;;; to do those tasks, and force the initial/main thread to run the
;;; GLUT event loop.
;;;

;;; Try to detect cases where we're already running some sort of event
;;; loop on OSX.  There are other ways to lose, of course.

#+darwin-target
(progn
  (eval-when (:compile-toplevel :execute)
    (use-interface-dir :cocoa))
  ;; If the IDE appears to be running, complain about that.
  (if (ignore-errors (find-symbol "*NSAPP*" "GUI"))
    (error "This is a GLUT example; it can't possibly work ~
                 in a GUI environment.")))

#+darwin-target
(progn
  (ccl:process-run-function
   "housekeeping"
   #'ccl::housekeeping-loop)
  (ccl:process-interrupt
   ccl::*initial-process*
   (lambda ()
     ;; CCL::%SET-TOPLEVEL is sort of like PROCESS-PRESET for the
     ;; initial process; CCL::TOPLEVEL is sort of like PROCESS-RESET
     ;; for that process.
     (ccl::%set-toplevel
      (lambda ()
       ;;; Set the OSX Window Server's notion of the name of the
       ;;; current process.
       (rlet ((psn #>ProcessSerialNumber))
         (#_GetCurrentProcess psn)
         (with-cstrs ((name "simple OpenGL example"))
           (ccl::external-call "_CPSSetProcessName" :address psn :address name :void)))
       (ccl::%set-toplevel nil)
       (main)))
     (ccl::toplevel))))

#-darwin-target
(process-run-function "GLUT Main" #'2dgasket::main)



