summaryrefslogtreecommitdiff
path: root/sly/render/model.scm
blob: 0eb07c06b0a0c73964a1336f91548eb8a40b164f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
;;; Sly
;;; Copyright (C) 2014 David Thompson <davet@gnu.org>
;;;
;;; Sly is free software: you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;;
;;; Sly is distributed in the hope that it will be useful, but WITHOUT
;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;;; License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program.  If not, see
;;; <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; OpenGL rendering state.
;;
;;; Code:

(define-module (sly render model)
  #:use-module (system foreign)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9)
  #:use-module (gl)
  #:use-module (gl low-level)
  #:use-module (sly math transform)
  #:use-module (sly math vector)
  #:use-module (sly math rect)
  #:use-module (sly render)
  #:use-module (sly render shader)
  #:use-module (sly render texture)
  #:use-module (sly render utils)
  #:use-module (sly render camera)
  #:use-module (sly render color)
  #:use-module (sly render mesh)
  #:export (make-model
            model
            model-inherit
            model? model-null?
            null-model
            model-mesh model-transform model-texture model-shader model-color
            model-blend-mode model-depth-test? model-sub-scene model-children
            draw-model
            model-paint
            model-blend
            model-group
            list->model
            model-move
            model-scale
            model-place))

;; Representation of a single OpenGL render call.
(define-record-type <model>
  (%make-model mesh transform texture shader color blend-mode
               depth-test? sub-scene children)
  model?
  (mesh model-mesh)
  (transform model-transform)
  (texture model-texture)
  (shader model-shader)
  (color model-color)
  (blend-mode model-blend-mode)
  (depth-test? model-depth-test?)
  (sub-scene model-sub-scene)
  (children model-children))

(define* (make-model #:optional #:key (mesh null-mesh)
                     (transform identity-transform) (texture null-texture)
                     (shader (load-default-shader)) (color white)
                     (blend-mode default-blend-mode) (depth-test? #t)
                     sub-scene (children '()))
  "Create a new model from MESH and the given rendering state.  When
rendering, TEXTURE and SHADER are bound, BLEND-MODE and DEPTH-TEST?
are set, and the COLOR uniform variable is set.  The presence of a
SUB-SCENE indicates that the model uses the scene's framebuffer as
it's texture, so it must be rendered first."
  (%make-model mesh transform texture shader color blend-mode
               depth-test? sub-scene children))

(define model make-model)

(define kwargs->alist
  (match-lambda
   (((? keyword? key) value . rest)
    (cons (cons (keyword->symbol key) value) (kwargs->alist rest)))
   (() '())))

(define model-inherit
  (let* ((fields (record-type-fields <model>))
         (field-indices (iota (length fields))))
    (lambda (original . kwargs)
      "Create a new model based on the fields of ORIGINAL, only
changing the fields specified in KWARGS."
      (let ((field+value (kwargs->alist kwargs)))
        (apply %make-model
               (map (lambda (field index)
                      (let ((override (find (match-lambda
                                             ((k . v)
                                              (eq? field k)))
                                            field+value)))
                        (if override
                            (cdr override)
                            (struct-ref original index))))
                    fields field-indices))))))

(define null-model
  (make-model #:shader null-shader))

(define (model-null? model)
  "Return #t if MODEL has no mesh and no children."
  (and (eq? (model-mesh model) null-mesh)
       (null? (model-children model))))

(define (set-transform-identity! t)
  (let ((matrix (transform-matrix t)))
    (array-set! matrix 1 0 0)
    (array-set! matrix 0 0 1)
    (array-set! matrix 0 0 2)
    (array-set! matrix 0 0 3)
    (array-set! matrix 0 1 0)
    (array-set! matrix 1 1 1)
    (array-set! matrix 0 1 2)
    (array-set! matrix 0 1 3)
    (array-set! matrix 0 2 0)
    (array-set! matrix 0 2 1)
    (array-set! matrix 1 2 2)
    (array-set! matrix 0 2 3)
    (array-set! matrix 0 3 0)
    (array-set! matrix 0 3 1)
    (array-set! matrix 0 3 2)
    (array-set! matrix 1 3 3)))

;; Avoid circular dependency.
(define draw-sub-scene
  (delay (module-ref (resolve-interface '(sly render scene)) 'draw-scene)))

(define (draw-model model gfx)
  "Render MODEL by applying its transformation, texture,
shader, vertex array, uniforms, blend mode, etc. using GFX."
  (match model
    ((? model-null? _)
     *unspecified*)
    (($ <model> mesh local-transform texture shader color blend-mode
        depth-test? sub-scene children)

     (when sub-scene
       (with-graphics-excursion gfx
         ((force draw-sub-scene) sub-scene gfx)))

     (with-model-view-excursion gfx
       (graphics-model-view-mul! gfx local-transform)
       (with-model-view-excursion gfx
         (graphics-model-view-mul! gfx (graphics-projection-transform gfx))
         (set-graphics-depth-test! gfx depth-test?)
         (set-graphics-blend-mode! gfx blend-mode)
         (set-graphics-shader! gfx shader)
         (set-graphics-mesh! gfx mesh)
         (set-graphics-texture! gfx texture)
         ;; TODO: Support user-defined uniforms.
         (uniform-set! shader "mvp" (graphics-model-view-transform gfx))
         (uniform-set! shader "color" color)
         (uniform-set! shader "use_texture" (not (texture-null? texture)))
         (glDrawElements (begin-mode triangles)
                         (mesh-length mesh)
                         (data-type unsigned-int)
                         %null-pointer))
       (for-each (lambda (child) (draw-model child gfx)) children)))))

;;;
;;; Utility Procedures
;;;

(define (model-paint color model)
  "Create a copy of MODEL, but with a new COLOR."
  (model-inherit model #:color color))

(define (model-blend blend-mode model)
  "Create a copy of MODEL, but with a new BLEND-MODE."
  (model-inherit model #:blend-mode blend-mode))

(define (model-group . children)
  "Create a new compound model containing the list of CHILDREN."
  (make-model #:children children))

(define (list->model children)
  "Create a new compound model containing the list of CHILDREN."
  (make-model #:children children))

(define (model-move position model)
  "Create a new group in which the list of CHILDREN are translated by
the vector POSITION."
  (model-inherit model #:transform (transform* (model-transform model)
                                               (translate position))))

(define (model-scale factor model)
  "Create a version of MODEL that is scaled up/down by FACTOR."
  (model-inherit model #:transform (transform* (model-transform model)
                                               (scale factor))))

(define (model-place transform model)
  "Create a new group in which the tranformation matrices of the
CHILDREN are multiplied by TRANSFORM."
  (model-inherit model #:transform (transform* (model-transform model)
                                               transform)))