;;; Chickadee Game Toolkit ;;; Copyright © 2021 David Thompson ;;; ;;; 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 ;;; . (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 * texLength; } else if(d >= length - m1) { // inside the right/top margin. return texLength - ((length - d) * texLength); } 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))))