summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--chickadee/graphics/9-patch.scm235
-rw-r--r--chickadee/graphics/sprite.scm130
-rw-r--r--doc/api.texi54
-rw-r--r--examples/9-patch.scm (renamed from examples/nine-patch.scm)4
5 files changed, 267 insertions, 157 deletions
diff --git a/Makefile.am b/Makefile.am
index 6dd8dfc..d397f3a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -75,6 +75,7 @@ SOURCES = \
chickadee/graphics/viewport.scm \
chickadee/graphics/framebuffer.scm \
chickadee/graphics/sprite.scm \
+ chickadee/graphics/9-patch.scm \
chickadee/graphics/font.scm \
chickadee/graphics/tiled.scm \
chickadee/graphics/particles.scm \
diff --git a/chickadee/graphics/9-patch.scm b/chickadee/graphics/9-patch.scm
new file mode 100644
index 0000000..833fa42
--- /dev/null
+++ b/chickadee/graphics/9-patch.scm
@@ -0,0 +1,235 @@
+;;; Chickadee Game Toolkit
+;;; Copyright © 2021 David Thompson <dthompson2@worcester.edu>
+;;;
+;;; Chickadee 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.
+;;;
+;;; Chickadee 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/>.
+
+(define-module (chickadee graphics 9-patch)
+ #:use-module (ice-9 match)
+ #:use-module (chickadee math matrix)
+ #:use-module (chickadee math rect)
+ #:use-module (chickadee math vector)
+ #:use-module (chickadee graphics blend)
+ #:use-module (chickadee graphics color)
+ #:use-module (chickadee graphics engine)
+ #:use-module (chickadee graphics shader)
+ #:use-module (chickadee graphics texture)
+ #:use-module (chickadee graphics buffer)
+ #:export (draw-9-patch*
+ draw-9-patch))
+
+(define-geometry-type <9-patch-vertex>
+ 9-patch-vertex-ref
+ 9-patch-vertex-set!
+ 9-patch-vertex-append!
+ (position vec2)
+ (distance vec2))
+
+(define-graphics-variable 9-patch-geometry
+ (make-geometry <9-patch-vertex> 4 #:index-capacity 6))
+(define-graphics-variable 9-patch-model-matrix (make-null-matrix4))
+(define-graphics-variable 9-patch-mvp-matrix (make-null-matrix4))
+(define-graphics-variable 9-patch-margins (make-null-rect))
+(define-graphics-variable 9-patch-shader
+ (strings->shader
+ "
+#ifdef GLSL330
+layout (location = 0) in vec2 position;
+layout (location = 1) in vec2 distance;
+#elif defined(GLSL130)
+in vec2 position;
+in vec2 distance;
+#elif defined(GLSL120)
+attribute vec2 position;
+attribute vec2 distance;
+#endif
+#ifdef GLSL120
+varying vec2 fragDistance;
+#else
+out vec2 fragDistance;
+#endif
+uniform mat4 mvp;
+
+void main(void) {
+ fragDistance = distance;
+ gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
+}
+"
+ "
+#ifdef GLSL120
+varying vec2 fragDistance;
+#else
+in vec2 fragDistance;
+#endif
+#ifdef GLSL330
+out vec4 fragColor;
+#endif
+uniform vec4 subtexture;
+uniform vec4 margins;
+uniform float width;
+uniform float height;
+uniform sampler2D colorTexture;
+uniform vec4 tint;
+uniform int mode;
+
+float patch(float d, float m0, float m1, float length, float texLength) {
+ if(d <= m0) { // inside the left/bottom margin.
+ return d;
+ } else if(d >= length - m1) { // inside the right/top margin.
+ return texLength - (length - d);
+ } else if(mode == 0) { // in the middle, stretch mode.
+ return mix(m0, texLength - m1, (d - m0) / (length - m0 - m1));
+ } else { // in the middle, tile mode.
+ return m0 + mod((d - m0), texLength - m0 - m1);
+ }
+}
+
+void main (void) {
+ vec2 texcoord = subtexture.xy;
+ texcoord.x += patch(fragDistance.x, margins.x, margins.y, width, subtexture.z);
+ texcoord.y += patch(fragDistance.y, margins.z, margins.w, height, subtexture.w);
+
+#ifdef GLSL330
+ fragColor = texture(colorTexture, texcoord) * tint;
+#else
+ gl_FragColor = texture2D(colorTexture, texcoord) * tint;
+#endif
+}
+"))
+
+(define* (draw-9-patch* texture
+ rect
+ matrix
+ #:key
+ (margin 0.0)
+ (top-margin margin)
+ (bottom-margin margin)
+ (left-margin margin)
+ (right-margin margin)
+ (mode 'stretch)
+ (tint white)
+ (blend-mode blend:alpha)
+ (texcoords (texture-gl-tex-rect texture)))
+ (let ((shader (graphics-variable-ref 9-patch-shader))
+ (geometry (graphics-variable-ref 9-patch-geometry))
+ (mvp (graphics-variable-ref 9-patch-mvp-matrix))
+ (margins (graphics-variable-ref 9-patch-margins)))
+ (let* ((w (rect-width rect))
+ (h (rect-height rect))
+ (tex-rect (texture-gl-rect texture))
+ (tw (rect-width tex-rect))
+ (th (rect-height tex-rect))
+ ;; Convert pixel coordinates to GL texture coordinates.
+ (w* (/ w tw))
+ (h* (/ h th)))
+ (with-geometry geometry
+ (let* ((x1 (rect-x rect))
+ (y1 (rect-y rect))
+ (x2 (+ x1 w))
+ (y2 (+ y1 h))
+ (s1 0.0)
+ (t1 0.0)
+ (s2 w*)
+ (t2 h*))
+ (9-patch-vertex-append! geometry
+ (x1 y1 s1 t1)
+ (x2 y1 s2 t1)
+ (x2 y2 s2 t2)
+ (x1 y2 s1 t2))
+ (geometry-index-append! geometry 0 3 2 0 2 1)))
+ ;; Convert pixel margin values to GL texture values.
+ (set-rect-x! margins (/ left-margin tw))
+ (set-rect-y! margins (/ right-margin tw))
+ (set-rect-width! margins (/ bottom-margin th))
+ (set-rect-height! margins (/ top-margin th))
+ (with-graphics-state ((g:blend-mode blend-mode)
+ (g:texture-0 texture))
+ (shader-apply shader
+ (geometry-vertex-array geometry)
+ #:width w*
+ #:height h*
+ #:subtexture texcoords
+ #:margins margins
+ #:mode (match mode
+ ('stretch 0)
+ ('tile 1))
+ #:tint tint
+ #:mvp (if matrix
+ (begin
+ (matrix4-mult! mvp matrix
+ (current-projection))
+ mvp)
+ (current-projection)))))))
+
+(define %null-vec2 (vec2 0.0 0.0))
+(define %default-scale (vec2 1.0 1.0))
+
+(define draw-9-patch
+ (let ((position (vec2 0.0 0.0))
+ (%rect (make-rect 0.0 0.0 0.0 0.0))
+ (matrix (make-null-matrix4)))
+ (lambda* (texture
+ rect
+ #:key
+ (margin 0.0)
+ (top-margin margin)
+ (bottom-margin margin)
+ (left-margin margin)
+ (right-margin margin)
+ (mode 'stretch)
+ (origin %null-vec2)
+ (rotation 0.0)
+ (scale %default-scale)
+ (blend-mode blend:alpha)
+ (tint white))
+ "Draw a 9-patch over the area RECT using TEXTURE whose
+stretchable/tileable patches are defined by the given margin
+measurements. The corners are never stretched/tiled, the left and
+right edges will be stretched/tiled vertically, the top and bottom
+edges may be stretched/tiled horizontally, and the center may be
+stretched/tiled in both directions.
+
+MODE may be either 'stretch' (the default) or 'tile'.
+
+MARGIN specifies the margin size for all sides of the 9-patch. To
+make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN,
+LEFT-MARGIN, and RIGHT-MARGIN arguments may be used.
+
+ORIGIN, ROTATION, and SCALE allow for arbitrary transformation of the
+9-patch.
+
+BLEND-MODE specifies the blending mode to use. Alpha blending is used
+by default.
+
+TINT specifies the color to tint the texture. White is used by
+default, which means there is no tint."
+ (set-rect-x! %rect 0.0)
+ (set-rect-y! %rect 0.0)
+ (set-rect-width! %rect (rect-width rect))
+ (set-rect-height! %rect (rect-height rect))
+ (set-vec2-x! position (rect-x rect))
+ (set-vec2-y! position (rect-y rect))
+ (matrix4-2d-transform! matrix
+ #:origin origin
+ #:position position
+ #:rotation rotation
+ #:scale scale)
+ (draw-9-patch* texture %rect matrix
+ #:top-margin top-margin
+ #:bottom-margin bottom-margin
+ #:left-margin left-margin
+ #:right-margin right-margin
+ #:mode mode
+ #:blend-mode blend-mode
+ #:tint tint))))
diff --git a/chickadee/graphics/sprite.scm b/chickadee/graphics/sprite.scm
index 0d6f838..8ab12f9 100644
--- a/chickadee/graphics/sprite.scm
+++ b/chickadee/graphics/sprite.scm
@@ -365,133 +365,3 @@ may be specified via the TEXTURE-REGION argument."
#:rotation rotation
#:scale scale)
(draw-sprite-batch* batch matrix #:blend-mode blend-mode)))
-
-
-;;;
-;;; Nine Patches
-;;;
-
-(define draw-nine-patch*
- (let ((%rect (make-rect 0.0 0.0 0.0 0.0))
- (texcoords (make-rect 0.0 0.0 0.0 0.0)))
- (lambda* (texture
- rect
- matrix
- #:key
- (margin 0.0)
- (top-margin margin)
- (bottom-margin margin)
- (left-margin margin)
- (right-margin margin)
- (blend-mode blend:alpha)
- (tint white))
- (let* ((x (rect-x rect))
- (y (rect-y rect))
- (w (rect-width rect))
- (h (rect-height rect))
- (border-x1 x)
- (border-y1 y)
- (border-x2 (+ x w))
- (border-y2 (+ y h))
- (fill-x1 (+ border-x1 left-margin))
- (fill-y1 (+ border-y1 bottom-margin))
- (fill-x2 (- border-x2 right-margin))
- (fill-y2 (- border-y2 top-margin))
- (prect (texture-gl-rect texture))
- (trect (texture-gl-tex-rect texture))
- (tw (rect-width prect))
- (th (rect-height prect))
- (border-s1 (rect-x trect))
- (border-t1 (rect-y trect))
- (border-s2 (+ (rect-x trect) (rect-width trect)))
- (border-t2 (+ (rect-y trect) (rect-height trect)))
- (fill-s1 (+ border-s1 (/ left-margin tw)))
- (fill-t1 (+ border-t1 (/ top-margin th)))
- (fill-s2 (- border-s2 (/ right-margin tw)))
- (fill-t2 (- border-t2 (/ bottom-margin th))))
- (define (draw-piece x1 y1 x2 y2 s1 t1 s2 t2)
- (set-rect-x! %rect x1)
- (set-rect-y! %rect y1)
- (set-rect-width! %rect (- x2 x1))
- (set-rect-height! %rect (- y2 y1))
- (set-rect-x! texcoords s1)
- (set-rect-y! texcoords t1)
- (set-rect-width! texcoords (- s2 s1))
- (set-rect-height! texcoords (- t2 t1))
- (draw-sprite* texture %rect matrix
- #:texcoords texcoords
- #:blend-mode blend-mode
- #:tint tint))
- ;; bottom-left
- (draw-piece border-x1 border-y1 fill-x1 fill-y1
- border-s1 fill-t2 fill-s1 border-t2)
- ;; bottom-center
- (draw-piece fill-x1 border-y1 fill-x2 fill-y1
- fill-s1 fill-t2 fill-s2 border-t2)
- ;; bottom-right
- (draw-piece fill-x2 border-y1 border-x2 fill-y1
- fill-s2 fill-t2 border-s2 border-t2)
- ;; center-left
- (draw-piece border-x1 fill-y1 fill-x1 fill-y2
- border-s1 fill-t2 fill-s1 fill-t1)
- ;; center
- (draw-piece fill-x1 fill-y1 fill-x2 fill-y2
- fill-s1 fill-t2 fill-s2 fill-t1)
- ;; center-right
- (draw-piece fill-x2 fill-y1 border-x2 fill-y2
- fill-s2 fill-t2 border-s2 fill-t1)
- ;; top-left
- (draw-piece border-x1 fill-y2 fill-x1 border-y2
- border-s1 border-t1 fill-s1 fill-t1)
- ;; top-center
- (draw-piece fill-x1 fill-y2 fill-x2 border-y2
- fill-s1 border-t1 fill-s2 fill-t1)
- ;; top-right
- (draw-piece fill-x2 fill-y2 border-x2 border-y2
- fill-s2 border-t1 border-s2 fill-t1)))))
-
-(define draw-nine-patch
- (let ((position (vec2 0.0 0.0))
- (%rect (make-rect 0.0 0.0 0.0 0.0))
- (matrix (make-null-matrix4)))
- (lambda* (texture
- rect
- #:key
- (margin 0.0)
- (top-margin margin) (bottom-margin margin)
- (left-margin margin) (right-margin margin)
- (origin %null-vec2)
- (rotation 0.0)
- (scale %default-scale)
- (blend-mode blend:alpha)
- (tint white))
- "Draw a \"nine patch\" sprite. A nine patch sprite renders
-TEXTURE on the rectangular area RECT whose stretchable areas are
-defined by the given margin measurements. The corners are never
-stretched, the left and right edges may be stretched vertically, the
-top and bottom edges may be stretched horizontally, and the center may
-be stretched in both directions. This rendering technique is
-particularly well suited for resizable windows and buttons in
-graphical user interfaces.
-
-MARGIN specifies the margin size for all sides of the nine patch. To
-make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN,
-LEFT-MARGIN, and RIGHT-MARGIN arguments may be used."
- (set-rect-x! %rect 0.0)
- (set-rect-y! %rect 0.0)
- (set-rect-width! %rect (rect-width rect))
- (set-rect-height! %rect (rect-height rect))
- (set-vec2-x! position (rect-x rect))
- (set-vec2-y! position (rect-y rect))
- (matrix4-2d-transform! matrix
- #:origin origin
- #:position position
- #:rotation rotation
- #:scale scale)
- (draw-nine-patch* texture %rect matrix
- #:top-margin top-margin
- #:bottom-margin bottom-margin
- #:left-margin left-margin
- #:right-margin right-margin
- #:blend-mode blend-mode
- #:tint tint))))
diff --git a/doc/api.texi b/doc/api.texi
index 5fa18b3..e99c89c 100644
--- a/doc/api.texi
+++ b/doc/api.texi
@@ -1586,6 +1586,7 @@ blocks to implement additional rendering techniques.
* Colors:: Such pretty colors...
* Textures:: 2D images.
* Sprites:: Draw 2D images.
+* 9-Patches:: Scalable bitmap boxes.
* Fonts:: Drawing text.
* Vector Paths:: Draw filled and stroked paths.
* Particles:: Pretty little flying pieces!
@@ -2041,48 +2042,51 @@ Render @var{batch} using @var{blend-mode}. Alpha blending is used by
default.
@end deffn
-With a basic sprite abstraction in place, it's possible to build other
-abstractions on top of it. One such example is the ``nine patch''. A
-nine patch is a sprite that can be rendered at various sizes without
-becoming distorted. This is achieved by dividing up the sprite into
-nine regions:
+@node 9-Patches
+@subsection 9-Patches
+
+A 9-patch is a method of rendering a texture so that it can be
+stretched to cover an area of any size without becoming distorted.
+This is achieved by dividing up the sprite into nine regions:
@itemize
@item
-the center, which can be scaled horizontally and vertically
+the center, which can be stretched or tiled horizontally and vertically
@item
-the four corners, which can never be scaled
+the four corners, which are never stretched or tiled
@item
-the left and right sides, which can be scaled vertically
+the left and right sides, which can be stretched or tiled vertically
@item
-the top and bottom sides, which can be scaled horizontally
+the top and bottom sides, which can be stretched or tiled horizontally
@end itemize
-The one caveat is that the bitmap regions must be designed in such a
-way so that they are not distorted when stretched along the affected
-axes. For example, that means that the top and bottom sides could
-have varying colored pixels vertically, but not horizontally.
-
The most common application of this technique is for graphical user
-interface widgets like buttons and dialog boxes. By using a nine
-patch, they can be rendered at any size without unappealing scaling
-artifacts.
+interface widgets like buttons and dialog boxes which are often
+dynamically resizable. By using a 9-patch, they can be rendered at
+any size without scaling artifacts.
@deffn {Procedure} draw-nine-patch texture rect @
[#:margin 0] [#:top-margin margin] [#:bottom-margin margin] @
- [#:left-margin margin] [#:right-margin margin] @
+ [#:left-margin margin] [#:right-margin margin] [#:mode stretch] @
[#:origin] [#:scale] [#:rotation] [#:blend-mode] @
[#:tint white]
-Draw a nine patch sprite. A nine patch sprite renders @var{texture}
-as a @var{width} x @var{height} rectangle whose stretchable areas are
-defined by the given margin measurements @var{top-margin},
-@var{bottom-margin}, @var{left-margin}, and @var{right-margin}. The
-@var{margin} argument may be used to configure all four margins at
-once.
+Draw a 9-patch over the area @var{rect} using @var{texture} whose
+stretchable/tileable patches are defined by the given margin
+measurements. The corners are never stretched/tiled, the left and
+right edges will be stretched/tiled vertically, the top and bottom
+edges may be stretched/tiled horizontally, and the center may be
+stretched/tiled in both directions.
+
+@var{mode} may be either @code{stretch} (the default) or @code{tile}.
+
+@var{margin} specifies the margin size for all sides of the 9-patch.
+To make margins of differing sizes, the @var{top-margin},
+@var{bottom-margin}, @var{left-margin}, and @var{right-margin}
+arguments may be used.
Refer to @code{draw-sprite} (@pxref{Sprites}) for information about
-the other arguments.
+the other arguments as they are the same.
@end deffn
@node Fonts
diff --git a/examples/nine-patch.scm b/examples/9-patch.scm
index d8988c8..451f079 100644
--- a/examples/nine-patch.scm
+++ b/examples/9-patch.scm
@@ -2,7 +2,7 @@
(chickadee math rect)
(chickadee math vector)
(chickadee graphics font)
- (chickadee graphics sprite)
+ (chickadee graphics 9-patch)
(chickadee graphics texture))
(define image #f)
@@ -11,7 +11,7 @@
(set! image (load-image "images/dialog-box.png")))
(define (draw alpha)
- (draw-nine-patch image (make-rect 192.0 192.0 256.0 96.0) #:margin 6)
+ (draw-9-patch image (make-rect 192.0 192.0 256.0 96.0) #:margin 4.0)
(draw-text "I am error." #v(200.0 266.0)))
(run-game #:load load #:draw draw)