summaryrefslogtreecommitdiff
path: root/chickadee/graphics/particles.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/particles.scm')
-rw-r--r--chickadee/graphics/particles.scm548
1 files changed, 329 insertions, 219 deletions
diff --git a/chickadee/graphics/particles.scm b/chickadee/graphics/particles.scm
index d8bd1b2..4d4b251 100644
--- a/chickadee/graphics/particles.scm
+++ b/chickadee/graphics/particles.scm
@@ -1,5 +1,5 @@
;;; Chickadee Game Toolkit
-;;; Copyright © 2018, 2021 David Thompson <dthompson2@worcester.edu>
+;;; Copyright © 2018, 2021, 2024 David Thompson <dthompson2@worcester.edu>
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
@@ -14,22 +14,24 @@
;;; limitations under the License.
(define-module (chickadee graphics particles)
+ #:use-module (chickadee data bytestruct)
+ #:use-module (chickadee graphics)
+ #:use-module (chickadee graphics buffer)
+ #:use-module (chickadee graphics color)
+ #:use-module (chickadee graphics pipeline)
+ #:use-module (chickadee graphics shader)
+ #:use-module (chickadee graphics texture)
+ #:use-module (chickadee math)
+ #:use-module (chickadee math matrix)
+ #:use-module (chickadee math rect)
+ #:use-module (chickadee math vector)
+ #:use-module (ice-9 match)
#:use-module (rnrs bytevectors)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-4)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
#:use-module (system foreign)
- #:use-module (chickadee math)
- #:use-module (chickadee math matrix)
- #:use-module (chickadee math rect)
- #:use-module (chickadee math vector)
- #:use-module (chickadee graphics blend)
- #:use-module (chickadee graphics buffer)
- #:use-module (chickadee graphics color)
- #:use-module (chickadee graphics engine)
- #:use-module (chickadee graphics shader)
- #:use-module (chickadee graphics texture)
#:export (make-particle-emitter
particle-emitter?
particle-emitter-spawn-area
@@ -40,7 +42,7 @@
particles?
particles-capacity
particles-size
- particles-texture
+ particles-texture-view
particles-blend-mode
particles-color
particles-spawn-area
@@ -75,45 +77,55 @@ indefinitely."
(let ((life (particle-emitter-life emitter)))
(and life (<= life 0))))
-(define-geometry-type <quad-vertex>
- quad-vertex-ref
- quad-vertex-set!
- quad-vertex-append!
- (position vec2)
- (texture vec2))
-
-(define-geometry-type <particle-vertex>
- particle-vertex-ref
- particle-vertex-set!
- particle-vertex-append!
- (position vec2)
- (velocity vec2)
- (acceleration vec2)
- (life int))
-
-(define-graphics-variable particles-shader
- (strings->shader
- "
+(define-bytestruct <particle-vertex>
+ (struct (position <vec2>)
+ (velocity <vec2>)
+ (accel <vec2>)
+ (life s32)))
+
+(define-bytestruct <quad-vertex>
+ (struct (position <vec2>)
+ (uv <vec2>)))
+
+(define-bytestruct <particle-uniforms>
+ (struct (matrix <matrix4>)
+ (lifetime s32)
+ (animation-rows s32)
+ (animation-columns s32)
+ (start-color <color>)
+ (end-color <color>)))
+
+(define-record-type <particle-state>
+ (make-particle-state uniforms sampler pipeline bindings matrix)
+ particle-state?
+ (uniforms particle-state-uniforms)
+ (sampler particle-state-sampler)
+ (pipeline particle-state-pipeline)
+ (bindings particle-state-bindings)
+ (matrix particle-state-matrix))
+
+(define-graphics-variable particle-state
+ (let ((shader (make-shader
+ (lambda (lang)
+ (values
+ "
#ifdef GLSL330
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 tex;
layout (location = 2) in vec2 offset;
layout (location = 3) in vec2 velocity;
-layout (location = 4) in vec2 acceleration;
-layout (location = 5) in float life;
+layout (location = 4) in float life;
#elif defined(GLSL130)
in vec2 position;
in vec2 tex;
in vec2 offset;
in vec2 velocity;
-in vec2 acceleration;
in float life;
#elif defined(GLSL120)
attribute vec2 position;
attribute vec2 tex;
attribute vec2 offset;
attribute vec2 velocity;
-attribute vec2 acceleration;
attribute float life;
#endif
#ifdef GLSL120
@@ -123,10 +135,23 @@ varying float t;
out vec2 fragTex;
out float t;
#endif
-uniform mat4 mvp;
+
+#ifdef GLSL120
+uniform mat4 matrix;
uniform int lifetime;
uniform int animationRows;
uniform int animationColumns;
+#else
+layout (std140) uniform Particles
+{
+ mat4 matrix;
+ int lifetime;
+ int animationRows;
+ int animationColumns;
+ vec4 startColor;
+ vec4 endColor;
+};
+#endif
void main(void) {
t = life / lifetime;
@@ -137,10 +162,10 @@ void main(void) {
float tw = 1.0 / animationColumns;
float th = 1.0 / animationRows;
fragTex = vec2(tx, ty) + tex * vec2(tw, th);
- gl_Position = mvp * vec4(position.xy + offset, 0.0, 1.0);
+ gl_Position = matrix * vec4(position + offset, 0.0, 1.0);
}
"
- "
+ "
#ifdef GLSL120
varying vec2 fragTex;
varying float t;
@@ -150,24 +175,75 @@ in float t;
#endif
#ifdef GLSL330
out vec4 fragColor;
+#else
+#define fragColor gl_FragColor
+#define texture texture2D
#endif
-uniform sampler2D color_texture;
+
+uniform sampler2D colorTexture;
+#ifdef GLSL120
uniform vec4 startColor;
uniform vec4 endColor;
-
-void main (void) {
-#ifdef GLSL330
- fragColor = mix(endColor, startColor, t) * texture(color_texture, fragTex);
#else
- gl_FragColor = mix(endColor, startColor, t) * texture2D(color_texture, fragTex);
+layout (std140) uniform Particles
+{
+ mat4 matrix;
+ int lifetime;
+ int animationRows;
+ int animationColumns;
+ vec4 startColor;
+ vec4 endColor;
+};
#endif
-}
-"))
-(define-graphics-variable mvp-matrix (make-null-matrix4))
+void main (void) {
+ fragColor = mix(endColor, startColor, t) * texture(colorTexture, fragTex);
+}
+")))))
+ (make-particle-state
+ (make-buffer (bytestruct-sizeof <particle-uniforms>)
+ #:name "Particle uniform buffer"
+ #:usage '(uniform))
+ (make-sampler #:name "Particle sampler")
+ (make-render-pipeline
+ #:name "Particles"
+ #:shader shader
+ #:color-target (make-color-target #:blend-mode blend:alpha)
+ #:vertex-layout
+ (vector
+ (make-vertex-buffer-layout
+ #:stride (* 4 4)
+ #:attributes (vector
+ ;; Position
+ (make-vertex-attribute
+ #:format 'float32x2)
+ ;; Texture
+ (make-vertex-attribute
+ #:format 'float32x2
+ #:offset (* 2 4))))
+ (make-vertex-buffer-layout
+ #:stride (* 7 4)
+ #:step-mode 'instance
+ #:attributes (vector
+ ;; Position
+ (make-vertex-attribute
+ #:format 'float32x2)
+ ;; Velocity
+ (make-vertex-attribute
+ #:format 'float32x2
+ #:offset (* 2 4))
+ ;; Life
+ (make-vertex-attribute
+ #:format 'sint32
+ #:offset (* 6 4)))))
+ #:binding-layout (vector (make-texture-layout)
+ (make-sampler-layout)
+ (make-buffer-layout)))
+ (make-vector 3 #f)
+ (make-null-matrix4))))
(define-record-type <particles>
- (%make-particles capacity size bv geometry
+ (%make-particles capacity size bv vertex-buffers index-buffer
texture animation-rows animation-columns
speed-range acceleration-range direction-range
blend-mode start-color end-color lifetime
@@ -176,7 +252,8 @@ void main (void) {
(capacity particles-capacity)
(size particles-size set-particles-size!)
(bv particles-bv)
- (geometry particles-geometry)
+ (vertex-buffers particles-vertex-buffers)
+ (index-buffer particles-index-buffer)
(texture particles-texture set-particles-texture!)
(animation-rows particles-animation-rows)
(animation-columns particles-animation-columns)
@@ -205,21 +282,21 @@ void main (void) {
(blend-mode blend:alpha)
(start-color white)
(end-color (make-color 0.0 0.0 0.0 0.0))
- (texture null-texture)
+ texture-view
(animation-rows 1)
(animation-columns 1)
- (width (if (texture-null? texture)
- 8.0
+ (width (if texture-view
(inexact->exact
(floor
- (/ (texture-width texture)
- animation-columns)))))
- (height (if (texture-null? texture)
- 8.0
+ (/ (texture-view-width texture-view)
+ animation-columns)))
+ 8.0))
+ (height (if texture-view
(inexact->exact
(floor
- (/ (texture-height texture)
- animation-rows)))))
+ (/ (texture-view-height texture-view)
+ animation-rows)))
+ 8.0))
(speed-range (vec2 0.1 1.0))
(acceleration-range (vec2 0.0 0.1))
(direction-range (vec2 0.0 (* 2 pi)))
@@ -239,8 +316,8 @@ Completely transparent by default for a fade-out effect. The color in
the middle of a particle's life will be an interpolation of
START-COLOR and END-COLOR.
-- TEXTURE: The texture applied to the particles. The texture may be
-subdivided into many animation frames.
+- TEXTURE-VIEW: The 2D texture applied to the particles. The texture
+may be subdivided into many animation frames.
- ANIMATION-ROWS: How many animation frame rows there are in the
texture. Default is 1.
@@ -273,27 +350,54 @@ default.
- SORT: 'youngest' if youngest particle should be drawn last or
'oldest' for the reverse. By default, no sorting is applied at all."
- (let ((geometry (make-geometry (list (list <quad-vertex> #:capacity 4)
- (list <particle-vertex> #:divisor 1))
- capacity
- #:index-capacity 6)))
- (with-geometry* geometry (<quad-vertex> 'index)
- (let ((hw (/ width 2.0))
- (hh (/ height 2.0)))
- (quad-vertex-append! geometry
- ((- hw) (- hh) 0.0 0.0)
- (hw (- hh) 1.0 0.0)
- (hw hh 1.0 1.0)
- ((- hw) hh 0.0 1.0)))
- (geometry-index-append! geometry 0 3 2 0 2 1))
+ (let* ((hw (/ width 2.0))
+ (hh (/ height 2.0))
+ (size (bytestruct-sizeof <quad-vertex>))
+ (quad-vertex
+ (bytevector->buffer
+ ;; TODO: Add some syntax for declaratively generating a
+ ;; bytevector of packed bytestructs.
+ (let ((bv (make-bytevector (* size 4))))
+ (bytestruct-pack! <quad-vertex>
+ (((position x) (- hw))
+ ((position y) (- hh))
+ ((uv x) 0.0)
+ ((uv y) 0.0))
+ bv 0)
+ (bytestruct-pack! <quad-vertex>
+ (((position x) hw)
+ ((position y) (- hh))
+ ((uv x) 1.0)
+ ((uv y) 0.0))
+ bv size)
+ (bytestruct-pack! <quad-vertex>
+ (((position x) hw)
+ ((position y) hh)
+ ((uv x) 1.0)
+ ((uv y) 1.0))
+ bv (* size 2))
+ (bytestruct-pack! <quad-vertex>
+ (((position x) (- hw))
+ ((position y) hh)
+ ((uv x) 0.0)
+ ((uv y) 1.0))
+ bv (* size 3))
+ bv)
+ #:name "Particles quad vertex buffer"))
+ (quad-index
+ (bytevector->buffer (u32vector 0 2 3 0 1 2)
+ #:name "Particles quad index buffer"
+ #:usage '(index)))
+ ;; 1 extra element as swap space for sorting.
+ (k (* (+ capacity 1) (bytestruct-sizeof <particle-vertex>)))
+ (particles (make-bytevector k))
+ (particles-vertex (make-buffer k #:name "Particles vertex buffer")))
(%make-particles capacity
- 0
- ;; 1 extra element as swap space for sorting.
- (make-bytevector (* (+ capacity 1)
- (geometry-type-stride
- <particle-vertex>)))
- geometry
- texture
+ 0 ; initial size
+ particles
+ (vector quad-vertex particles-vertex)
+ quad-index
+ texture-view
animation-rows
animation-columns
speed-range
@@ -304,152 +408,158 @@ default.
end-color
lifetime
sort
+ ;; No emitters initially.
'())))
(define (update-particles particles)
"Advance the simulation of PARTICLES."
- (let* ((speed-range (particles-speed-range particles))
- (acceleration-range (particles-acceleration-range particles))
- (direction-range (particles-direction-range particles))
- (sort (particles-sort particles))
- (lifetime (particles-lifetime particles))
- (float-ref bytevector-ieee-single-native-ref)
- (float-set! bytevector-ieee-single-native-set!)
- (int-ref bytevector-s32-native-ref)
- (int-set! bytevector-s32-native-set!)
- (y-offset 4)
- (dx-offset 8)
- (dy-offset 12)
- (ddx-offset 16)
- (ddy-offset 20)
- (life-offset 24))
- (let* ((bv (particles-bv particles))
- (stride (geometry-type-stride <particle-vertex>))
- (capacity (particles-capacity particles)))
- ;; Update existing particles, removing dead ones.
- (let loop ((i 0)
- (size (particles-size particles)))
- (if (< i size)
- (let* ((offset (* i stride))
- (life (- (int-ref bv (+ offset life-offset)) 1)))
- (if (<= life 0)
- (let ((new-size (- size 1)))
- (bytevector-copy! bv (* new-size stride) bv offset stride)
- (loop i new-size))
- (let ((x (float-ref bv offset))
- (y (float-ref bv (+ offset y-offset)))
- (dx (float-ref bv (+ offset dx-offset)))
- (dy (float-ref bv (+ offset dy-offset)))
- (ddx (float-ref bv (+ offset ddx-offset)))
- (ddy (float-ref bv (+ offset ddy-offset))))
- (int-set! bv (+ offset life-offset) life)
- (float-set! bv offset (+ x dx))
- (float-set! bv (+ offset y-offset) (+ y dy))
- (float-set! bv (+ offset dx-offset) (+ dx ddx))
- (float-set! bv (+ offset dy-offset) (+ dy ddy))
- (loop (+ i 1) size))))
- (set-particles-size! particles size)))
- ;; Add particles from each active emitter and then remove
- ;; emitters that have completed.
- (let ((sx (vec2-x speed-range))
- (sy (vec2-y speed-range))
- (ax (vec2-x acceleration-range))
- (ay (vec2-y acceleration-range))
- (dx (vec2-x direction-range))
- (dy (vec2-y direction-range))
- (emitters (particles-emitters particles)))
- (define (emit emitter any-done?)
- (let* ((spawn-area (particle-emitter-spawn-area emitter))
- (rate (particle-emitter-rate emitter))
- (rx (rect-x spawn-area))
- (ry (rect-y spawn-area))
- (rw (rect-width spawn-area))
- (rh (rect-height spawn-area))
- (start (particles-size particles))
- (end (min (+ start rate) capacity)))
- (let loop ((i start))
- (if (< i end)
- (let* ((offset (* i stride))
- (speed (+ (* (random:uniform) (- sy sx)) sx))
- (accel (+ (* (random:uniform) (- ay ax)) ax))
- (dir (+ (* (random:uniform) (- dy dx)) dx))
- (dir-x (cos dir))
- (dir-y (sin dir)))
- (float-set! bv offset (+ rx (* (random:uniform) rw)))
- (float-set! bv (+ offset y-offset)
- (+ ry (* (random:uniform) rh)))
- (float-set! bv (+ offset dx-offset) (* dir-x speed))
- (float-set! bv (+ offset dy-offset) (* dir-y speed))
- (float-set! bv (+ offset ddx-offset) (* dir-x accel))
- (float-set! bv (+ offset ddy-offset) (* dir-y accel))
- (int-set! bv (+ offset life-offset) lifetime)
- (loop (+ i 1)))
- (begin
- (set-particles-size! particles end)
- (update-particle-emitter emitter)
- (or any-done? (particle-emitter-done? emitter)))))))
- (when (fold emit #f emitters)
- (set-particles-emitters! particles
- (remove particle-emitter-done? emitters))))
- ;; Sort particles.
- (when sort
- (let ((compare (cond
- ((eq? sort 'young)
- (lambda (i j)
- (< (int-ref bv (+ i life-offset))
- (int-ref bv (+ j life-offset)))))
- ((eq? sort 'old)
- (lambda (i j)
- (> (int-ref bv (+ i life-offset))
- (int-ref bv (+ j life-offset)))))
- (else
- (error "unknown particle sorting method" sort))))
- (tmp (* (particles-capacity particles) stride)))
- (define (swap i j)
- (bytevector-copy! bv i bv tmp stride)
- (bytevector-copy! bv j bv i stride)
- (bytevector-copy! bv tmp bv j stride))
- ;; In the benchmarks I've done, insertion sort has
- ;; performed much better than quicksort here. The number
- ;; of comparisons and swaps is much fewer.
- (define (sort start end)
- (let outer ((i (+ start stride)))
- (when (< i end)
- (let inner ((j i))
- (when (and (> j start)
- (compare j (- j stride)))
- (swap (- j stride) j)
- (inner (- j stride))))
- (outer (+ i stride)))))
- (sort 0 (* (particles-size particles) stride))))
- (with-geometry* (particles-geometry particles) (<particle-vertex>)
- (geometry-import! (particles-geometry particles) <particle-vertex> bv 0
- (particles-size particles))))))
+ (match particles
+ (($ <particles> capacity (? exact-integer? size) bv #(_ vertex) _ _ _ _
+ speed-range acceleration-range direction-range
+ _ _ _ lifetime sort emitters)
+ (let ((stride (bytestruct-sizeof <particle-vertex>)))
+ ;; Update existing particles, removing dead ones.
+ (let loop ((i 0) (size size))
+ (if (< i size)
+ (let ((offset (* i stride)))
+ (call-with-values (lambda ()
+ (bytestruct-unpack <particle-vertex>
+ ((position x)
+ (position y)
+ (velocity x)
+ (velocity y)
+ (accel x)
+ (accel y)
+ (life))
+ bv offset))
+ (lambda (x y dx dy ddx ddy life)
+ (let ((life (- life 1)))
+ (if (<= life 0)
+ (let ((size (- size 1)))
+ (bytevector-copy! bv (* size stride) bv offset stride)
+ (loop i size))
+ (begin
+ (bytestruct-pack! <particle-vertex>
+ (((position x) (+ x dx))
+ ((position y) (+ y dy))
+ ((velocity x) (+ dx ddx))
+ ((velocity y) (+ dy ddy))
+ ((life) life))
+ bv offset)
+ (loop (+ i 1) size)))))))
+ (set-particles-size! particles size)))
+ ;; Add particles from each active emitter and then remove
+ ;; emitters that have completed.
+ (let ((sx (vec2-x speed-range))
+ (sy (vec2-y speed-range))
+ (ax (vec2-x acceleration-range))
+ (ay (vec2-y acceleration-range))
+ (dx (vec2-x direction-range))
+ (dy (vec2-y direction-range))
+ (emitters (particles-emitters particles)))
+ (define (emit emitter any-done?)
+ (let* ((spawn-area (particle-emitter-spawn-area emitter))
+ (rate (particle-emitter-rate emitter))
+ (rx (rect-x spawn-area))
+ (ry (rect-y spawn-area))
+ (rw (rect-width spawn-area))
+ (rh (rect-height spawn-area))
+ (start (particles-size particles))
+ (end (min (+ start rate) capacity)))
+ (let loop ((i start))
+ (if (< i end)
+ (let* ((offset (* i stride))
+ (speed (+ (* (random:uniform) (- sy sx)) sx))
+ (accel (+ (* (random:uniform) (- ay ax)) ax))
+ (dir (+ (* (random:uniform) (- dy dx)) dx))
+ (dir-x (cos dir))
+ (dir-y (sin dir)))
+ (bytestruct-pack! <particle-vertex>
+ (((position x) (+ rx (* (random:uniform) rw)))
+ ((position y) (+ ry (* (random:uniform) rh)))
+ ((velocity x) (* dir-x speed))
+ ((velocity y) (* dir-y speed))
+ ((accel x) (* dir-x accel))
+ ((accel y) (* dir-y accel))
+ ((life) lifetime))
+ bv offset)
+ (loop (+ i 1)))
+ (begin
+ (set-particles-size! particles end)
+ (update-particle-emitter emitter)
+ (or any-done? (particle-emitter-done? emitter)))))))
+ (when (fold emit #f emitters)
+ (set-particles-emitters! particles
+ (remove particle-emitter-done? emitters))))
+ ;; Sort particles.
+ (when sort
+ (let ((compare
+ (match sort
+ ('young
+ (lambda (i j)
+ (< (bytestruct-unpack <particle-vertex> ((life)) bv i)
+ (bytestruct-unpack <particle-vertex> ((life)) bv j))))
+ ('old
+ (lambda (i j)
+ (> (bytestruct-unpack <particle-vertex> ((life)) bv i)
+ (bytestruct-unpack <particle-vertex> ((life)) bv j))))
+ (_
+ (error "invalid particle sorting method" sort))))
+ (tmp (* (particles-capacity particles) stride)))
+ (define (swap i j)
+ (bytevector-copy! bv i bv tmp stride)
+ (bytevector-copy! bv j bv i stride)
+ (bytevector-copy! bv tmp bv j stride))
+ ;; In the benchmarks I've done, insertion sort has
+ ;; performed much better than quicksort here. The number
+ ;; of comparisons and swaps is much fewer.
+ (define (sort start end)
+ (let outer ((i (+ start stride)))
+ (when (< i end)
+ (let inner ((j i))
+ (when (and (> j start)
+ (compare j (- j stride)))
+ (swap (- j stride) j)
+ (inner (- j stride))))
+ (outer (+ i stride)))))
+ (sort 0 (* (particles-size particles) stride))))
+ (let* ((k (* stride (particles-size particles)))
+ (dst (map-buffer vertex 'write 0 k)))
+ (bytevector-copy! bv 0 dst 0 k)
+ (unmap-buffer vertex))))))
+;; TODO: Blend mode
(define (draw-particles* particles matrix)
"Render PARTICLES with MATRIX applied."
- (let ((shader (graphics-variable-ref particles-shader))
- (mvp (graphics-variable-ref mvp-matrix))
- (geometry (particles-geometry particles)))
- (with-graphics-state ((g:blend-mode (particles-blend-mode particles))
- (g:texture-0 (particles-texture particles)))
- (shader-apply/instanced shader
- (geometry-vertex-array geometry)
- (particles-size particles)
- #:mvp (if matrix
- (begin
- (matrix4-mult! mvp matrix
- (current-projection))
- mvp)
- (current-projection))
- #:start-color (particles-start-color particles)
- #:end-color (particles-end-color particles)
- #:lifetime (particles-lifetime particles)
- #:animation-rows
- (particles-animation-rows particles)
- #:animation-columns
- (particles-animation-columns particles)))))
+ (match particles
+ (($ <particles> _ size _ vertex-buffers index-buffer texture-view
+ rows columns _ _ _ blend-mode start-color end-color
+ lifetime)
+ (match (graphics-variable-ref particle-state)
+ (($ <particle-state> uniforms sampler pipeline bindings mvp)
+ (matrix4-mult! mvp matrix (current-projection))
+ (let ((bv (map-buffer uniforms 'write 0
+ (bytestruct-sizeof <particle-uniforms>))))
+ (bytestruct-pack! <particle-uniforms>
+ (((matrix) mvp)
+ ((lifetime) lifetime)
+ ((animation-rows) rows)
+ ((animation-columns) columns)
+ ((start-color) start-color)
+ ((end-color) end-color))
+ bv 0)
+ (unmap-buffer uniforms))
+ (vector-set! bindings 0 texture-view)
+ (vector-set! bindings 1 sampler)
+ (vector-set! bindings 2 uniforms)
+ (draw 6
+ #:instances size
+ #:pipeline pipeline
+ #:index-buffer index-buffer
+ #:vertex-buffers vertex-buffers
+ #:bindings bindings))))))
+(define %default-matrix (make-identity-matrix4))
(define (draw-particles particles)
"Render PARTICLES."
- (draw-particles* particles #f))
+ (draw-particles* particles %default-matrix))