summaryrefslogtreecommitdiff
path: root/chickadee/graphics/9-patch.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/9-patch.scm')
-rw-r--r--chickadee/graphics/9-patch.scm235
1 files changed, 235 insertions, 0 deletions
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))))