render: sprite: Allow passing custom transformation matrices.
authorDavid Thompson <dthompson2@worcester.edu>
Fri, 5 Jan 2018 20:56:26 +0000 (15:56 -0500)
committerDavid Thompson <dthompson2@worcester.edu>
Fri, 5 Jan 2018 20:56:26 +0000 (15:56 -0500)
* chickadee/render/sprite.scm (draw-sprite-unbatched,
draw-sprite-batched): Replace 'scale' and 'rotation' arguments with a
'matrix' argument.
(sprite-batch-add!): Ditto. Also, correctly transform sprite
vertices.  This was previously not handling rotations properly.
(%default-offset): New variable.
(draw-sprite, draw-nine-patch): Change default values for 'rotation'
and 'scale'.  Add 'matrix' and 'offset' arguments.
* chickadee/render/font.scm (%default-offset): New variable.
(draw-text): Change default values for 'rotation' and 'scale'.  Add
'offset' and 'matrix' arguments.

chickadee/render/font.scm
chickadee/render/sprite.scm

index 31670ad..bac5056 100644 (file)
@@ -295,10 +295,17 @@ extension must be either .xml or .fnt."
 (define (font-ref font char)
   (hashv-ref (font-chars font) char))
 
+(define %default-offset (vec2 0.0 0.0))
+
 (define draw-text
   (let ((cursor (vec2 0.0 0.0))
         (char-pos (vec2 0.0 0.0)))
-    (lambda* (font text position #:key scale rotation (blend-mode 'alpha))
+    (lambda* (font text position #:key
+                   (rotation 0)
+                   (scale 1.0)
+                   matrix
+                   (offset %default-offset)
+                   (blend-mode 'alpha))
       "Draw the string TEXT with the first character starting at
 POSITION using FONT."
       ;; TODO: Respect kerning.
@@ -310,8 +317,10 @@ POSITION using FONT."
           (set-vec2-y! char-pos (+ (vec2-y cursor) (vec2-y offset)))
           (draw-sprite (font-char-texture-region char)
                        char-pos
+                       #:offset offset
                        #:scale scale
                        #:rotation rotation
+                       #:matrix matrix
                        #:blend-mode blend-mode)
           ;; Move forward to where the next character needs to be drawn.
           (set-vec2-x! cursor
index b07a969..38b1594 100644 (file)
@@ -80,15 +80,13 @@ void main (void) {
                                #:attributes
                                `((0 . ,(force position-buffer))
                                  (1 . ,(force texcoord-buffer))))))
-         (tmp-matrix (make-null-matrix4))
-         (mvp (make-null-matrix4))
-         (position (vec2 0 0)))
-    (lambda (texture region scale rotation blend-mode shader texture-region)
+         (mvp (make-null-matrix4)))
+    (lambda (texture region world-matrix blend-mode shader texture-region)
       (with-mapped-typed-buffer (force position-buffer)
-        (let* ((x1 0)
-               (y1 0)
-               (x2 (rect-width region))
-               (y2 (rect-height region))
+        (let* ((x1 (rect-x region))
+               (y1 (rect-y region))
+               (x2 (+ x1 (rect-width region)))
+               (y2 (+ y1 (rect-height region)))
                (bv (typed-buffer-data (force position-buffer))))
           (f32vector-set! bv 0 x1)
           (f32vector-set! bv 1 y1)
@@ -112,21 +110,15 @@ void main (void) {
           (f32vector-set! bv 5 t2)
           (f32vector-set! bv 6 s1)
           (f32vector-set! bv 7 t2)))
-      (matrix4-identity! mvp)
-      (when rotation
-        (matrix4-rotate-z! tmp-matrix rotation)
-        (matrix4-mult! mvp mvp tmp-matrix))
-      (when scale
-        (matrix4-scale! tmp-matrix scale)
-        (matrix4-mult! mvp mvp tmp-matrix))
-      (set-vec2-x! position (rect-x region))
-      (set-vec2-y! position (rect-y region))
-      (matrix4-translate! tmp-matrix position)
-      (matrix4-mult! mvp mvp tmp-matrix)
-      (matrix4-mult! mvp mvp (current-projection))
       (with-blend-mode blend-mode
         (with-texture 0 texture
-          (gpu-apply shader (force vertex-array) #:mvp mvp))))))
+          (gpu-apply shader (force vertex-array)
+                     #:mvp (if world-matrix
+                               (begin
+                                 (matrix4-mult! mvp world-matrix
+                                                (current-projection))
+                                 mvp)
+                               (current-projection))))))))
 
 \f
 ;;;
@@ -223,17 +215,16 @@ void main (void) {
         (sprite-batch-reset! batch)))))
 
 (define sprite-batch-add!
-  (let ((tmp-matrix (make-null-matrix4))
-        (matrix (make-null-matrix4))
-        (position (vec2 0.0 0.0))
-        (world1 (vec2 0.0 0.0))
+  (let ((world1 (vec2 0.0 0.0))
         (world2 (vec2 0.0 0.0))
+        (world3 (vec2 0.0 0.0))
+        (world4 (vec2 0.0 0.0))
         (offset-bv (make-u32vector 1)))
     (define (set-offset offset)
       (u32vector-set! offset-bv 0 offset))
     (define (offset)
       (u32vector-ref offset-bv 0))
-    (lambda (batch texture region scale rotation blend-mode
+    (lambda (batch texture region world-matrix blend-mode
                    shader texture-region)
       ;; Expand the buffers when necessary.
       (when (sprite-batch-full? batch)
@@ -251,93 +242,74 @@ void main (void) {
         (let* ((indices (typed-buffer-data (sprite-batch-index-buffer batch)))
                (vertices (typed-buffer-data (sprite-batch-position-buffer batch)))
                (texcoords (typed-buffer-data (sprite-batch-texture-buffer batch)))
-               (rx (rect-x region))
-               (ry (rect-y region))
-               (local-x1 0.0)
-               (local-y1 0.0)
-               (local-x2 (rect-width region))
-               (local-y2 (rect-height region))
+               (local-x1 (rect-x region))
+               (local-y1 (rect-y region))
+               (local-x2 (+ local-x1 (rect-width region)))
+               (local-y2 (+ local-y1 (rect-height region)))
                (s1 (rect-left texture-region))
                (t1 (rect-bottom texture-region))
                (s2 (rect-right texture-region))
                (t2 (rect-top texture-region)))
-          (if (or rotation scale)
-              (begin
-                (matrix4-identity! matrix)
-                (when rotation
-                  (matrix4-rotate-z! tmp-matrix rotation)
-                  (matrix4-mult! matrix matrix tmp-matrix))
-                (when scale
-                  (matrix4-scale! tmp-matrix scale)
-                  (matrix4-mult! matrix matrix tmp-matrix))
-                (set-vec2-x! position rx)
-                (set-vec2-y! position ry)
-                (matrix4-translate! tmp-matrix position)
-                (matrix4-mult! matrix matrix tmp-matrix)
-                (set-vec2-x! world1 local-x1)
-                (set-vec2-y! world1 local-y1)
-                (set-vec2-x! world2 local-x2)
-                (set-vec2-y! world2 local-y2)
-                (transform! matrix world1)
-                (transform! matrix world2))
-              (begin
-                (set-vec2-x! world1 (+ local-x1 rx))
-                (set-vec2-y! world1 (+ local-y1 ry))
-                (set-vec2-x! world2 (+ local-x2 rx))
-                (set-vec2-y! world2 (+ local-y2 ry))))
-          (let ((world-x1 (vec2-x world1))
-                (world-y1 (vec2-y world1))
-                (world-x2 (vec2-x world2))
-                (world-y2 (vec2-y world2)))
-            ;; Add indices.
-            (set-offset (* size 4))
-            (let ((index-vertex-offset (offset)))
-              (set-offset (* size 6))
-              (u32vector-set! indices (offset) index-vertex-offset)
-              (u32vector-set! indices (+ (offset) 1) (+ index-vertex-offset 3))
-              (u32vector-set! indices (+ (offset) 2) (+ index-vertex-offset 2))
-              (u32vector-set! indices (+ (offset) 3) index-vertex-offset)
-              (u32vector-set! indices (+ (offset) 4) (+ index-vertex-offset 2))
-              (u32vector-set! indices (+ (offset) 5) (+ index-vertex-offset 1)))
-            ;; Add vertices.
-            (set-offset (* size 8)) ;; 4 vertices, 2 floats per vertex
-            ;; Bottom-left
-            (f32vector-set! vertices (offset) world-x1)
-            (f32vector-set! vertices (+ (offset) 1) world-y1)
-            ;; Bottom-right
-            (f32vector-set! vertices (+ (offset) 2) world-x2)
-            (f32vector-set! vertices (+ (offset) 3) world-y1)
-            ;; Top-right
-            (f32vector-set! vertices (+ (offset) 4) world-x2)
-            (f32vector-set! vertices (+ (offset) 5) world-y2)
-            ;; Top-left
-            (f32vector-set! vertices (+ (offset) 6) world-x1)
-            (f32vector-set! vertices (+ (offset) 7) world-y2)
-            ;; Add texture coordinates.
-            (set-offset (* size 8))
-            ;; Bottom-left
-            (f32vector-set! texcoords (offset) s1)
-            (f32vector-set! texcoords (+ (offset) 1) t1)
-            ;; Bottom-right
-            (f32vector-set! texcoords (+ (offset) 2) s2)
-            (f32vector-set! texcoords (+ (offset) 3) t1)
-            ;; Top-right
-            (f32vector-set! texcoords (+ (offset) 4) s2)
-            (f32vector-set! texcoords (+ (offset) 5) t2)
-            ;; Top-left
-            (f32vector-set! texcoords (+ (offset) 6) s1)
-            (f32vector-set! texcoords (+ (offset) 7) t2)
-            (set-sprite-batch-size! batch (1+ size))))))))
+          (set-vec2-x! world1 local-x1)
+          (set-vec2-y! world1 local-y1)
+          (set-vec2-x! world2 local-x2)
+          (set-vec2-y! world2 local-y1)
+          (set-vec2-x! world3 local-x2)
+          (set-vec2-y! world3 local-y2)
+          (set-vec2-x! world4 local-x1)
+          (set-vec2-y! world4 local-y2)
+          (when world-matrix
+            (transform! world-matrix world1)
+            (transform! world-matrix world2)
+            (transform! world-matrix world3)
+            (transform! world-matrix world4))
+          ;; Add indices.
+          (set-offset (* size 4))
+          (let ((index-vertex-offset (offset)))
+            (set-offset (* size 6))
+            (u32vector-set! indices (offset) index-vertex-offset)
+            (u32vector-set! indices (+ (offset) 1) (+ index-vertex-offset 3))
+            (u32vector-set! indices (+ (offset) 2) (+ index-vertex-offset 2))
+            (u32vector-set! indices (+ (offset) 3) index-vertex-offset)
+            (u32vector-set! indices (+ (offset) 4) (+ index-vertex-offset 2))
+            (u32vector-set! indices (+ (offset) 5) (+ index-vertex-offset 1)))
+          ;; Add vertices.
+          (set-offset (* size 8)) ;; 4 vertices, 2 floats per vertex
+          ;; Bottom-left
+          (f32vector-set! vertices (offset) (vec2-x world1))
+          (f32vector-set! vertices (+ (offset) 1) (vec2-y world1))
+          ;; Bottom-right
+          (f32vector-set! vertices (+ (offset) 2) (vec2-x world2))
+          (f32vector-set! vertices (+ (offset) 3) (vec2-y world2))
+          ;; Top-right
+          (f32vector-set! vertices (+ (offset) 4) (vec2-x world3))
+          (f32vector-set! vertices (+ (offset) 5) (vec2-y world3))
+          ;; Top-left
+          (f32vector-set! vertices (+ (offset) 6) (vec2-x world4))
+          (f32vector-set! vertices (+ (offset) 7) (vec2-y world4))
+          ;; Add texture coordinates.
+          (set-offset (* size 8))
+          ;; Bottom-left
+          (f32vector-set! texcoords (offset) s1)
+          (f32vector-set! texcoords (+ (offset) 1) t1)
+          ;; Bottom-right
+          (f32vector-set! texcoords (+ (offset) 2) s2)
+          (f32vector-set! texcoords (+ (offset) 3) t1)
+          ;; Top-right
+          (f32vector-set! texcoords (+ (offset) 4) s2)
+          (f32vector-set! texcoords (+ (offset) 5) t2)
+          ;; Top-left
+          (f32vector-set! texcoords (+ (offset) 6) s1)
+          (f32vector-set! texcoords (+ (offset) 7) t2)
+          (set-sprite-batch-size! batch (1+ size)))))))
 
 (define *batch?* #f)
 (define %batch (delay (make-sprite-batch 256)))
 
-(define (draw-sprite-batched texture region
-                             scale rotation blend-mode shader
+(define (draw-sprite-batched texture region world-matrix blend-mode shader
                              texture-region)
-  (sprite-batch-add! (force %batch) texture region
-                     scale rotation blend-mode shader
-                     texture-region))
+  (sprite-batch-add! (force %batch) texture region world-matrix blend-mode
+                     shader texture-region))
 
 (define-syntax-rule (with-batched-sprites body ...)
   "Use batched rendering for all draw-sprite calls within BODY."
@@ -361,22 +333,40 @@ void main (void) {
   (@@ (chickadee render texture) texture-region-gl-rect))
 
 (define %default-texcoords (make-rect 0.0 0.0 1.0 1.0))
+(define %default-offset (vec2 0.0 0.0))
 
 (define draw-sprite
-  (let ((rect (make-rect 0.0 0.0 0.0 0.0)))
-    (lambda* (texture region #:key
-                      scale rotation (blend-mode 'alpha)
-                      (texcoords
-                       (if (texture-region? texture)
-                           (texture-region-gl-rect texture)
-                           %default-texcoords))
-                      (shader (force default-shader)))
+  (let ((rect (make-rect 0.0 0.0 0.0 0.0))
+        (tmp-matrix (make-null-matrix4))
+        (%matrix (make-null-matrix4)))
+    (lambda* (texture
+              region
+              #:key
+              (scale 1.0)
+              (rotation 0)
+              matrix
+              (offset %default-offset)
+              (blend-mode 'alpha)
+              (texcoords
+               (if (texture-region? texture)
+                   (texture-region-gl-rect texture)
+                   %default-texcoords))
+              (shader (force default-shader)))
       "Draw TEXTURE over the area defined by the rect REGION.  Instead
 of a rect, REGION may be a vec2 representing the position of the
 sprite, in which case the width and height of the sprite corresponds
 to the size of the texture.  TEXTURE may be a texture or a texture
-region.  ROTATION specifies by how many radians the sprite will be
-rotated.  SCALE specifies the scaling factor.  By default, alpha
+region.
+
+ROTATION specifies the angle to rotate the sprite, in radians.  SCALE
+specifies the scaling factor.  No scaling or rotation is applied by
+default.  Alternatively, MATRIX may be specified, in which case
+ROTATION and SCALE are ignored and the given transformation matrix is
+used instead.  All transformations are applied relative to the lower
+left corner of the sprite by default.  This can be changed by
+specifying an OFFSET vector.
+
+By default, alpha
 blending is used but can be changed by setting BLEND-MODE.  Finally,
 advanced users may pass SHADER to change the way the sprite is
 rendered entirely."
@@ -386,20 +376,66 @@ rendered entirely."
              (texture (if (texture-region? texture)
                           (texture-region-texture texture)
                           texture))
-             (region (if (rect? region)
-                         region
-                         (begin
-                           (rect-move-vec2! rect region)
-                           (set-rect-width! rect (f32vector-ref size 0))
-                           (set-rect-height! rect (f32vector-ref size 1))
-                           rect))))
+             (matrix (cond
+                      (matrix matrix) ; user-specified matrix
+                      ((or rotation scale) ; compute matrix on-the-fly
+                       (matrix4-identity! %matrix)
+                       (unless (zero? rotation)
+                         (matrix4-rotate-z! tmp-matrix rotation)
+                         (matrix4-mult! %matrix %matrix tmp-matrix))
+                       (unless (= scale 1.0)
+                         (matrix4-scale! tmp-matrix scale)
+                         (matrix4-mult! %matrix %matrix tmp-matrix))
+                       (matrix4-translate! tmp-matrix region)
+                       (matrix4-mult! %matrix %matrix tmp-matrix)
+                       %matrix)
+                      ;; No transformation needed, in which case we
+                      ;; use no matrix at all in order to save cycles
+                      ;; and not waste time multiplying against the
+                      ;; identity matrix.
+                      (else #f))))
+        (cond
+         ((and (rect? region)
+               (not matrix)
+               (zero? rotation) ; no rotation
+               (= scale 1.0)) ; no scale
+          ;; We won't be using a transformation matrix.
+          ;; Just apply the offset.
+          (set-rect-x! rect (- (rect-x region) (vec2-x offset)))
+          (set-rect-y! rect (- (rect-y region) (vec2-y offset)))
+          (set-rect-width! rect (rect-width region))
+          (set-rect-height! rect (rect-height region)))
+         ((rect? region)
+          ;; We will be using a transformation matrix, so
+          ;; ignore the region's X and Y coordinates as
+          ;; those will be accounted for in the
+          ;; translation matrix.
+          (set-rect-x! rect (- (vec2-x offset)))
+          (set-rect-y! rect (- (vec2-y offset)))
+          (set-rect-width! rect (rect-width region))
+          (set-rect-height! rect (rect-height region)))
+         ((and (not matrix)
+               (zero? rotation)
+               (= scale 1.0))
+          ;; No region specified and no transformation
+          ;; matrix. Use texture width/height for the
+          ;; dimensions of the region.
+          (set-rect-x! rect (- (vec2-x region) (vec2-x offset)))
+          (set-rect-y! rect (- (vec2-y region) (vec2-y offset)))
+          (set-rect-width! rect (f32vector-ref size 0))
+          (set-rect-height! rect (f32vector-ref size 1)))
+         (else
+          ;; No region specified but we will be using a
+          ;; transformation matrix.
+          (set-rect-x! rect (- (vec2-x offset)))
+          (set-rect-y! rect (- (vec2-y offset)))
+          (set-rect-width! rect (f32vector-ref size 0))
+          (set-rect-height! rect (f32vector-ref size 1))))
         (if *batch?*
-            (draw-sprite-batched texture region
-                                 scale rotation blend-mode shader
-                                 texcoords)
-            (draw-sprite-unbatched texture region
-                                   scale rotation blend-mode shader
-                                   texcoords))))))
+            (draw-sprite-batched texture rect matrix blend-mode
+                                 shader texcoords)
+            (draw-sprite-unbatched texture rect matrix blend-mode
+                                   shader texcoords))))))
 
 \f
 ;;;
@@ -412,7 +448,11 @@ rendered entirely."
     (lambda* (texture region #:key (margin 0)
                       (top-margin margin) (bottom-margin margin)
                       (left-margin margin) (right-margin margin)
-                      scale rotation (blend-mode 'alpha)
+                      (offset %default-offset)
+                      (rotation 0)
+                      (scale 1.0)
+                      matrix
+                      (blend-mode 'alpha)
                       (shader (force default-shader)))
       "Draw a \"nine patch\" sprite.  A nine patch sprite renders
 TEXTURE as a WIDTH x HEIGHT rectangle whose stretchable areas are
@@ -463,8 +503,10 @@ LEFT-MARGIN, and RIGHT-MARGIN arguments may be used."
           (set-rect-height! trect (- t2 t1))
           (draw-sprite texture rect
                        #:texcoords trect
+                       #:offset offset
                        #:scale scale
                        #:rotation rotation
+                       #:matrix matrix
                        #:blend-mode blend-mode
                        #:shader shader))
         (with-batched-sprites