Skip to content

Commit 15e81dc

Browse files
committed
SDF Rect renderer
1 parent e6ca07a commit 15e81dc

File tree

26 files changed

+583
-419
lines changed

26 files changed

+583
-419
lines changed

common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,16 @@ sealed class VertexAttrib(
6464

6565
@Suppress("ClassName")
6666
open class Group(vararg val attributes: VertexAttrib) {
67-
object POS_UV : Group(Vec2, Vec2)
67+
object POS_UV : Group(
68+
Vec2, Vec2
69+
)
6870

6971
// GUI
70-
object FONT : Group(Vec3, Vec2, Color)
72+
object FONT : Group(
73+
Vec3, Vec2, Color
74+
)
7175

72-
object RECT_FILLED : Group(
76+
object RECT : Group(
7377
Vec3, Vec2, Color
7478
)
7579

@@ -86,7 +90,9 @@ sealed class VertexAttrib(
8690
Vec3, Color
8791
)
8892

89-
object PARTICLE : Group(Vec3, Vec2, Color)
93+
object PARTICLE : Group(
94+
Vec3, Vec2, Color
95+
)
9096

9197
val stride = attributes.sumOf { attribute ->
9298
attribute.size

common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import com.lambda.util.math.MathUtils.toInt
3232
import com.lambda.util.math.Vec2d
3333
import org.lwjgl.glfw.GLFW
3434

35-
abstract class AbstractGUIRenderer(
35+
open class AbstractGUIRenderer(
3636
attribGroup: VertexAttrib.Group,
3737
val shader: Shader
3838
) {
@@ -49,13 +49,13 @@ abstract class AbstractGUIRenderer(
4949
}
5050
}
5151

52-
protected fun render(
52+
fun render(
5353
shade: Boolean = false,
54-
block: VertexPipeline.() -> Unit
54+
block: VertexPipeline.(Shader) -> Unit
5555
) {
5656
shader.use()
5757

58-
block(pipeline)
58+
block(pipeline, shader)
5959

6060
shader["u_Shade"] = shade.toInt().toDouble()
6161
if (shade) {

common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt renamed to common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Lambda
2+
* Copyright 2025 Lambda
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -15,11 +15,10 @@
1515
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
package com.lambda.graphics.renderer.gui.font
18+
package com.lambda.graphics.renderer.gui
1919

2020
import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib
2121
import com.lambda.graphics.pipeline.VertexBuilder
22-
import com.lambda.graphics.renderer.gui.AbstractGUIRenderer
2322
import com.lambda.graphics.renderer.gui.font.core.GlyphInfo
2423
import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get
2524
import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.graphics.renderer.gui
19+
20+
import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib
21+
import com.lambda.graphics.shader.Shader.Companion.shader
22+
import com.lambda.util.math.Rect
23+
import com.lambda.util.math.Vec2d
24+
import java.awt.Color
25+
import kotlin.math.min
26+
27+
object RectRenderer {
28+
private val filled = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_filled"))
29+
private val outline = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_outline"))
30+
private val glow = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_glow"))
31+
32+
fun filledRect(
33+
rect: Rect,
34+
leftTopRadius: Double = 0.0,
35+
rightTopRadius: Double = 0.0,
36+
rightBottomRadius: Double = 0.0,
37+
leftBottomRadius: Double = 0.0,
38+
leftTop: Color = Color.WHITE,
39+
rightTop: Color = Color.WHITE,
40+
rightBottom: Color = Color.WHITE,
41+
leftBottom: Color = Color.WHITE,
42+
shade: Boolean = false,
43+
) = filled.putRect(
44+
rect,
45+
0.0,
46+
shade,
47+
leftTopRadius,
48+
rightTopRadius,
49+
rightBottomRadius,
50+
leftBottomRadius,
51+
leftTop,
52+
rightTop,
53+
rightBottom,
54+
leftBottom,
55+
)
56+
57+
fun outlineRect(
58+
rect: Rect,
59+
width: Double = 1.0,
60+
leftTopRadius: Double = 0.0,
61+
rightTopRadius: Double = 0.0,
62+
rightBottomRadius: Double = 0.0,
63+
leftBottomRadius: Double = 0.0,
64+
leftTop: Color = Color.WHITE,
65+
rightTop: Color = Color.WHITE,
66+
rightBottom: Color = Color.WHITE,
67+
leftBottom: Color = Color.WHITE,
68+
shade: Boolean = false,
69+
) {
70+
if (width < 0.01) return
71+
72+
outline.putRect(
73+
rect,
74+
width * 0.5,
75+
shade,
76+
leftTopRadius,
77+
rightTopRadius,
78+
rightBottomRadius,
79+
leftBottomRadius,
80+
leftTop,
81+
rightTop,
82+
rightBottom,
83+
leftBottom,
84+
)
85+
}
86+
87+
fun glowRect(
88+
rect: Rect,
89+
outerSpread: Double = 1.0,
90+
innerSpread: Double = 1.0,
91+
leftTopInnerRadius: Double = 0.0,
92+
rightTopInnerRadius: Double = 0.0,
93+
rightBottomInnerRadius: Double = 0.0,
94+
leftBottomInnerRadius: Double = 0.0,
95+
leftTopOuterRadius: Double = 0.0,
96+
rightTopOuterRadius: Double = 0.0,
97+
rightBottomOuterRadius: Double = 0.0,
98+
leftBottomOuterRadius: Double = 0.0,
99+
leftTop: Color = Color.WHITE,
100+
rightTop: Color = Color.WHITE,
101+
rightBottom: Color = Color.WHITE,
102+
leftBottom: Color = Color.WHITE,
103+
shade: Boolean = false,
104+
) {
105+
if (outerSpread < 0.01 && innerSpread < 0.01) return
106+
107+
val pos1 = rect.leftTop
108+
val pos2 = rect.rightBottom
109+
110+
val size = pos2 - pos1
111+
val halfSize = size * 0.5
112+
val maxRadius = min(halfSize.x, halfSize.y)
113+
114+
fun Double.clampRadius() =
115+
this.coerceAtMost(maxRadius)
116+
.coerceAtLeast(0.0)
117+
118+
glow.shader.use()
119+
glow.shader["u_InnerRectWidth"] = innerSpread.coerceAtLeast(1.0)
120+
glow.shader["u_InnerRoundLeftTop"] = leftTopInnerRadius .coerceAtLeast(leftTopOuterRadius) .clampRadius()
121+
glow.shader["u_InnerRoundLeftBottom"] = leftBottomInnerRadius .coerceAtLeast(leftBottomOuterRadius) .clampRadius()
122+
glow.shader["u_InnerRoundRightBottom"] = rightBottomInnerRadius.coerceAtLeast(rightBottomOuterRadius).clampRadius()
123+
glow.shader["u_InnerRoundRightTop"] = rightTopInnerRadius .coerceAtLeast(rightTopOuterRadius) .clampRadius()
124+
125+
glow.putRect(
126+
rect,
127+
outerSpread.coerceAtLeast(1.0),
128+
shade,
129+
leftTopOuterRadius,
130+
rightTopOuterRadius,
131+
rightBottomOuterRadius,
132+
leftBottomOuterRadius,
133+
leftTop,
134+
rightTop,
135+
rightBottom,
136+
leftBottom,
137+
)
138+
}
139+
140+
private fun AbstractGUIRenderer.putRect(
141+
rect: Rect,
142+
expandIn: Double,
143+
shade: Boolean,
144+
leftTopRadius: Double,
145+
rightTopRadius: Double,
146+
rightBottomRadius: Double,
147+
leftBottomRadius: Double,
148+
leftTop: Color,
149+
rightTop: Color,
150+
rightBottom: Color,
151+
leftBottom: Color,
152+
) = render(shade) { shader ->
153+
val pos1 = rect.leftTop
154+
val pos2 = rect.rightBottom
155+
156+
val expand = expandIn.coerceAtLeast(0.0) + 1
157+
val smoothing = 0.5
158+
159+
val p1 = pos1 - expand - smoothing
160+
val p2 = pos2 + expand + smoothing
161+
162+
val size = pos2 - pos1
163+
val halfSize = size * 0.5
164+
val maxRadius = min(halfSize.x, halfSize.y)
165+
166+
val uv1 = Vec2d(
167+
-expand / size.x,
168+
-expand / size.y
169+
)
170+
171+
val uv2 = Vec2d(
172+
1.0 + (expand / size.x),
173+
1.0 + (expand / size.y)
174+
)
175+
176+
fun Double.clampRadius() =
177+
this.coerceAtMost(maxRadius)
178+
.coerceAtLeast(0.0)
179+
180+
// Size of the rectangle
181+
shader["u_Size"] = size
182+
183+
// Round radius
184+
shader["u_RoundLeftTop"] = leftTopRadius.clampRadius()
185+
shader["u_RoundLeftBottom"] = leftBottomRadius.clampRadius()
186+
shader["u_RoundRightBottom"] = rightBottomRadius.clampRadius()
187+
shader["u_RoundRightTop"] = rightTopRadius.clampRadius()
188+
189+
// For glow & outline only
190+
shader["u_RectWidth"] = expandIn
191+
192+
upload {
193+
buildQuad(
194+
vertex {
195+
vec3m(p1.x, p1.y, 0.0).vec2(uv1.x, uv1.y).color(leftTop)
196+
},
197+
vertex {
198+
vec3m(p1.x, p2.y, 0.0).vec2(uv1.x, uv2.y).color(leftBottom)
199+
},
200+
vertex {
201+
vec3m(p2.x, p2.y, 0.0).vec2(uv2.x, uv2.y).color(rightBottom)
202+
},
203+
vertex {
204+
vec3m(p2.x, p1.y, 0.0).vec2(uv2.x, uv1.y).color(rightTop)
205+
}
206+
)
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)