[orx-shade-styles] Write comments on demos
This commit is contained in:
@@ -1,20 +1,45 @@
|
|||||||
package org.openrndr.extra.shadestyles.fills
|
package org.openrndr.extra.shadestyles.fills
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies how to fill shapes with the gradient
|
||||||
|
*/
|
||||||
enum class FillFit {
|
enum class FillFit {
|
||||||
|
/** Deforms the gradient to match the bounds of the shape */
|
||||||
STRETCH,
|
STRETCH,
|
||||||
|
|
||||||
|
/** Resizes the gradient to cover the bounds of the shape */
|
||||||
COVER,
|
COVER,
|
||||||
|
|
||||||
|
/** Resizes the gradient to fit inside the bounds of the shape */
|
||||||
CONTAIN
|
CONTAIN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies what units are coordinates given in
|
||||||
|
*/
|
||||||
enum class FillUnits {
|
enum class FillUnits {
|
||||||
|
/** Normalized coordinates, with (0.5, 0.5) at the center of the gradient. */
|
||||||
BOUNDS,
|
BOUNDS,
|
||||||
|
|
||||||
|
/** Screen coordinates in pixels */
|
||||||
WORLD,
|
WORLD,
|
||||||
|
|
||||||
VIEW,
|
VIEW,
|
||||||
|
|
||||||
SCREEN,
|
SCREEN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies how to extend a gradient when outside the normalized range
|
||||||
|
*/
|
||||||
enum class SpreadMethod {
|
enum class SpreadMethod {
|
||||||
|
/** Stretches the edge color */
|
||||||
PAD,
|
PAD,
|
||||||
|
|
||||||
|
/** Mirrors the color in a ping-pong fashion, as if traveling through the gradient back and forth */
|
||||||
REFLECT,
|
REFLECT,
|
||||||
|
|
||||||
|
/** Loops through the gradient as needed */
|
||||||
REPEAT
|
REPEAT
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ import org.openrndr.math.transforms.transform
|
|||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animated demonstration on how to use the `clip` shade style to mask-out
|
||||||
|
* part of an image (or anything else drawn while the shade style is active).
|
||||||
|
* The clipping uses the `CONTAIN` fit mode.
|
||||||
|
*
|
||||||
|
* This example uses a rotating `star`-shaped clipping with 24 sides.
|
||||||
|
* Other available clipping shapes are `circle`, `rectangle`, `line` and `ellipse`.
|
||||||
|
*
|
||||||
|
* Press a mouse button to toggle the `feather` property between 0.0 and 0.5.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -25,10 +35,8 @@ fun main() = application {
|
|||||||
|
|
||||||
val image = loadImage("demo-data/images/image-001.png")
|
val image = loadImage("demo-data/images/image-001.png")
|
||||||
extend {
|
extend {
|
||||||
|
|
||||||
val grid = drawer.bounds.grid(3, 3)
|
val grid = drawer.bounds.grid(3, 3)
|
||||||
for ((index, cell) in grid.flatten().withIndex()) {
|
for ((index, cell) in grid.flatten().withIndex()) {
|
||||||
|
|
||||||
drawer.shadeStyle = clip {
|
drawer.shadeStyle = clip {
|
||||||
clipFit = FillFit.CONTAIN
|
clipFit = FillFit.CONTAIN
|
||||||
feather = gf
|
feather = gf
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ import org.openrndr.math.transforms.transform
|
|||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animated demonstration on how to use the `clip` shade style to mask-out
|
||||||
|
* part of an image (or anything else drawn while the shade style is active).
|
||||||
|
* The clipping uses different fit modes on each row, and different aspect
|
||||||
|
* ratios in each column.
|
||||||
|
*
|
||||||
|
* This example uses a rotating `star`-shaped clipping with 24 sides.
|
||||||
|
* Other available clipping shapes are `circle`, `rectangle`, `line` and `ellipse`.
|
||||||
|
*
|
||||||
|
* Press a mouse button to toggle the `feather` property between 0.0 and 0.5.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -31,7 +42,7 @@ fun main() = application {
|
|||||||
for ((index, cell) in grid.flatten().withIndex()) {
|
for ((index, cell) in grid.flatten().withIndex()) {
|
||||||
|
|
||||||
drawer.shadeStyle = clip {
|
drawer.shadeStyle = clip {
|
||||||
clipFit = FillFit.entries[index/3]
|
clipFit = FillFit.entries[index / 3]
|
||||||
feather = gf
|
feather = gf
|
||||||
|
|
||||||
clipTransform = transform {
|
clipTransform = transform {
|
||||||
@@ -43,12 +54,13 @@ fun main() = application {
|
|||||||
star {
|
star {
|
||||||
radius = 0.5
|
radius = 0.5
|
||||||
center = Vector2(0.5, 0.5)
|
center = Vector2(0.5, 0.5)
|
||||||
sharpness = cos( 2 * PI * index / 9.0 + seconds) * 0.25 + 0.5
|
sharpness = cos(2 * PI * index / 9.0 + seconds) * 0.25 + 0.5
|
||||||
sides = 24
|
sides = 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val acell = when(val i = index%3) {
|
// Use sub() on squares to create vertical or horizontal rectangles
|
||||||
|
val acell = when (val i = index % 3) {
|
||||||
1 -> cell.sub(0.0..0.5, 0.0..1.0)
|
1 -> cell.sub(0.0..0.5, 0.0..1.0)
|
||||||
2 -> cell.sub(0.0..1.0, 0.0..0.5)
|
2 -> cell.sub(0.0..1.0, 0.0..0.5)
|
||||||
else -> cell
|
else -> cell
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ import org.openrndr.extra.shapes.primitives.placeIn
|
|||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.transforms.transform
|
import org.openrndr.math.transforms.transform
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animated demonstration on how to use the `clip` shade style to mask-out
|
||||||
|
* part of an image (or anything else drawn while the shade style is active).
|
||||||
|
* The clipping uses different fit modes on each row, and different aspect
|
||||||
|
* ratios in each column.
|
||||||
|
*
|
||||||
|
* This example uses a rotating `ellipse`-shaped clipping.
|
||||||
|
* Other available clipping shapes are `circle`, `rectangle`, `line` and `star`.
|
||||||
|
*
|
||||||
|
* Press a mouse button to toggle the `feather` property between 0.0 and 0.5.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -6,19 +6,12 @@ import org.openrndr.extra.shadestyles.fills.clip.clip
|
|||||||
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main entry point of the application that sets up the visual program.
|
* Demonstrates how to combine two shade styles
|
||||||
|
* (a conic gradient and a rounded star clipping)
|
||||||
|
* by using the `+` operator.
|
||||||
*
|
*
|
||||||
* This method creates a graphical program with a 720x720 window and uses a rotating
|
* The design is animated by applying a rotation transformation matrix
|
||||||
* gradient-shaded rectangle as the primary visual element. It demonstrates the use
|
* based in the `seconds` variable.
|
||||||
* of gradient shading and clipping through a compositional approach.
|
|
||||||
*
|
|
||||||
* The method performs the following actions:
|
|
||||||
* 1. Configures the application window size.
|
|
||||||
* 2. Constructs a conic gradient with a rotation of 54 degrees and full circular coverage.
|
|
||||||
* 3. Creates a star-shaped clip with configurable sharpness, radius, and number of sides.
|
|
||||||
* 4. Combines the gradient and clip into a composite shading style.
|
|
||||||
* 5. Defines a program loop where the rectangle with the gradient and clip combination
|
|
||||||
* rotates around the center of the canvas while being redrawn continuously.
|
|
||||||
*/
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
|||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to create 4 animated gradient shade-styles with 5 colors:
|
||||||
|
* - a linear gradient
|
||||||
|
* - a stellar gradient
|
||||||
|
* - a radial gradient
|
||||||
|
* - a linear gradient with `SpreadMethod.REPEAT`
|
||||||
|
* Each gradient style has different adjustable attributes.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -25,7 +33,6 @@ fun main() {
|
|||||||
linear {
|
linear {
|
||||||
start = Vector2(0.1, 0.1).rotate(seconds * 36.0, Vector2(0.5, 0.5))
|
start = Vector2(0.1, 0.1).rotate(seconds * 36.0, Vector2(0.5, 0.5))
|
||||||
end = Vector2(0.9, 0.9).rotate(seconds * 36.0, Vector2(0.5, 0.5))
|
end = Vector2(0.9, 0.9).rotate(seconds * 36.0, Vector2(0.5, 0.5))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawer.rectangle(0.0, 0.0, 360.0, 360.0)
|
drawer.rectangle(0.0, 0.0, 360.0, 360.0)
|
||||||
|
|||||||
@@ -8,6 +8,18 @@ import org.openrndr.extra.shadestyles.fills.FillUnits
|
|||||||
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||||
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An application with two animated layers of slightly different stellar shade styles.
|
||||||
|
*
|
||||||
|
* The bottom layer features a rectangle, while the top layer includes a large text
|
||||||
|
* repeated 5 times.
|
||||||
|
*
|
||||||
|
* The only different between the two shade styles is a minor change in the `levelWarp`
|
||||||
|
* function, which is used to alter the gradient's level (its normalized `t` value)
|
||||||
|
* based on the current coordinates being processed, and the original level at this location.
|
||||||
|
*
|
||||||
|
* Without this difference, the shader would look identical, and the text would be invisible.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -24,18 +36,22 @@ fun main() {
|
|||||||
quantization = 10
|
quantization = 10
|
||||||
fillUnits = FillUnits.WORLD
|
fillUnits = FillUnits.WORLD
|
||||||
spreadMethod = SpreadMethod.REFLECT
|
spreadMethod = SpreadMethod.REFLECT
|
||||||
levelWarpFunction = """float levelWarp(vec2 p, float level) { return level + cos(p.x*0.01 + level)*0.1; } """
|
levelWarpFunction = """
|
||||||
|
float levelWarp(vec2 p, float level) {
|
||||||
|
return level + cos(p.x * 0.01 + level) * 0.1;
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
stellar {
|
stellar {
|
||||||
radius = drawer.bounds.width/4.0
|
radius = drawer.bounds.width / 4.0
|
||||||
center = drawer.bounds.position(0.5, 0.0)
|
center = drawer.bounds.position(0.5, 0.0)
|
||||||
sides = 6
|
sides = 6
|
||||||
sharpness = 0.5
|
sharpness = 0.5
|
||||||
rotation = seconds * 36.0
|
rotation = seconds * 36.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawer.rectangle(drawer.bounds)
|
drawer.rectangle(drawer.bounds)
|
||||||
|
|
||||||
drawer.shadeStyle = gradient<ColorRGBa> {
|
drawer.shadeStyle = gradient<ColorRGBa> {
|
||||||
stops[0.0] = ColorRGBa.BLUE_STEEL
|
stops[0.0] = ColorRGBa.BLUE_STEEL
|
||||||
stops[0.75] = ColorRGBa.WHITE
|
stops[0.75] = ColorRGBa.WHITE
|
||||||
@@ -44,10 +60,14 @@ fun main() {
|
|||||||
quantization = 10
|
quantization = 10
|
||||||
fillUnits = FillUnits.WORLD
|
fillUnits = FillUnits.WORLD
|
||||||
spreadMethod = SpreadMethod.REFLECT
|
spreadMethod = SpreadMethod.REFLECT
|
||||||
levelWarpFunction = """float levelWarp(vec2 p, float level) { return level + 0.1 + cos(p.x*0.01 + level)*0.1; } """
|
levelWarpFunction = """
|
||||||
|
float levelWarp(vec2 p, float level) {
|
||||||
|
return level + 0.1 + cos(p.x * 0.01 + level) * 0.1;
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
stellar {
|
stellar {
|
||||||
radius = drawer.bounds.width/4.0
|
radius = drawer.bounds.width / 4.0
|
||||||
center = drawer.bounds.position(0.5, 0.0)
|
center = drawer.bounds.position(0.5, 0.0)
|
||||||
sides = 6
|
sides = 6
|
||||||
sharpness = 0.5
|
sharpness = 0.5
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
|||||||
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to create a rainbow-like rotating `conic` gradient in `OKHSV` color space.
|
||||||
|
* The gradient consists of ten evenly spaced colors, achieved by shifting the hue of a base color.
|
||||||
|
* Since the conic gradient covers 360 degrees, changing the `spreadMethod` does not affect the result.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ import org.openrndr.extra.shapes.primitives.grid
|
|||||||
import org.openrndr.extra.shapes.primitives.placeIn
|
import org.openrndr.extra.shapes.primitives.placeIn
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a 3x3 grid of gradients demonstrating how the same gradient can look different depending on
|
||||||
|
* the aspect ratio of the target shape and the fit method used.
|
||||||
|
*
|
||||||
|
* The first column features a vertical rectangle.
|
||||||
|
* The second one, a square, and the third one a horizontal rectangle.
|
||||||
|
*
|
||||||
|
* The rows feature the different fit methods: `FillFit.STRETCH`, `FillFit.COVER` and `FillFit.CONTAIN`.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -6,7 +6,18 @@ import org.openrndr.extra.color.presets.BLUE_STEEL
|
|||||||
import org.openrndr.extra.shadestyles.fills.FillUnits
|
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||||
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||||
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveals the effect of using quantization on a `conic` gradient.
|
||||||
|
* By using a `quantization` of 10 we get 9 color bands.
|
||||||
|
*
|
||||||
|
* Notice how the center of the `conic` gradient is specified in
|
||||||
|
* screen coordinates. To make this possible, we need to set the
|
||||||
|
* `fillUnits` to `FillUnits.WORLD`. By default, the center of
|
||||||
|
* the gradient coordinates is `Vector2(0.5, 0.5)`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ import kotlin.math.PI
|
|||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to animate the `radiusX` and `radiusY` elliptic gradient arguments separately.
|
||||||
|
* They are animated in a circular fashion, making the ellipse transition between a thin vertical shape,
|
||||||
|
* a round shape, and a thin horizontal shape.
|
||||||
|
*
|
||||||
|
* The `SpreadMethod.REPEAT` setting makes the gradient cover the available space repeating the gradient
|
||||||
|
* as many times as needed.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
|||||||
import org.openrndr.extra.shapes.primitives.grid
|
import org.openrndr.extra.shapes.primitives.grid
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A design with 48 vertical bands with gradients. Each one has a unique `quantization`
|
||||||
|
* value based on the index of the band. All bands have 2 color `stops`:
|
||||||
|
* `WHITE` at the top (position 0.0), and `BLACK` near the bottom (near position 1.0),
|
||||||
|
* with the exact value depending on the `quantization` value.
|
||||||
|
*
|
||||||
|
* Demonstrates how to produce a quantized gradient with a specific number of equal color bands.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -25,7 +33,7 @@ fun main() = application {
|
|||||||
drawer.shadeStyle = gradient<ColorRGBa> {
|
drawer.shadeStyle = gradient<ColorRGBa> {
|
||||||
quantization = index + 2
|
quantization = index + 2
|
||||||
stops[0.0] = ColorRGBa.WHITE
|
stops[0.0] = ColorRGBa.WHITE
|
||||||
stops[ (quantization) / (quantization+1.0)] = ColorRGBa.BLACK
|
stops[(quantization) / (quantization + 1.0)] = ColorRGBa.BLACK
|
||||||
|
|
||||||
fillUnits = FillUnits.BOUNDS
|
fillUnits = FillUnits.BOUNDS
|
||||||
fillFit = FillFit.COVER
|
fillFit = FillFit.COVER
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ import org.openrndr.math.Vector2
|
|||||||
import org.openrndr.math.asDegrees
|
import org.openrndr.math.asDegrees
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the creation of a grid-based design with 13x13 cells, each with an elliptic gradient
|
||||||
|
* pointing towards the center of the window. The center cell features a circular gradient (by having
|
||||||
|
* `radiusX` equal to `radiusY`). The farther a cell is from the center, the higher the aspect ratio
|
||||||
|
* of the ellipse is, becoming closer to a line than to a circle near the corners.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() =
|
fun main() =
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -32,10 +39,10 @@ fun main() =
|
|||||||
|
|
||||||
spreadMethod = SpreadMethod.REPEAT
|
spreadMethod = SpreadMethod.REPEAT
|
||||||
elliptic {
|
elliptic {
|
||||||
val v = Vector2(x-6.0, y-6.0)
|
val v = Vector2(x - 6.0, y - 6.0)
|
||||||
rotation = atan2(y- 6.0, x - 6.0).asDegrees + 180.0
|
rotation = atan2(y - 6.0, x - 6.0).asDegrees + 180.0
|
||||||
radiusX = 1.0
|
radiusX = 1.0
|
||||||
radiusY = 1.0 / (1.0 + v.length*0.25)
|
radiusY = 1.0 / (1.0 + v.length * 0.25)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawer.rectangle(cell)
|
drawer.rectangle(cell)
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
|||||||
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates two types of shade styles: `pattern` and `luma`.
|
||||||
|
*
|
||||||
|
* The `pattern` shade style is used to generate a checkers-pattern.
|
||||||
|
*
|
||||||
|
* This example also loads and draws an image using the `luma` shade style
|
||||||
|
* to map pixel brightnesses to gradient colors. Dark colors are
|
||||||
|
* mapped to transparent, revealing the checkers-pattern behind it
|
||||||
|
* in parts of the image.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import org.openrndr.application
|
|||||||
import org.openrndr.draw.loadImage
|
import org.openrndr.draw.loadImage
|
||||||
import org.openrndr.extra.shadestyles.fills.image.imageFill
|
import org.openrndr.extra.shadestyles.fills.image.imageFill
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimal demonstration of the `imageFill` shade style, used to texture
|
||||||
|
* shapes using a loaded image (or generated color buffer).
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -6,20 +6,25 @@ import org.openrndr.extra.shadestyles.fills.image.imageFill
|
|||||||
import org.openrndr.math.transforms.transform
|
import org.openrndr.math.transforms.transform
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `imageFill` shade style, applied to 10 concentric
|
||||||
|
* circles. The rotation of each circle depends on the cosine of time, with
|
||||||
|
* a varying time offset applied per circle, for a fun wavy effect.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
height = 720
|
height = 720
|
||||||
}
|
}
|
||||||
program {
|
program {
|
||||||
var img = loadImage("demo-data/images/image-001.png")
|
val img = loadImage("demo-data/images/image-001.png")
|
||||||
extend {
|
extend {
|
||||||
for (i in 0 until 10) {
|
for (i in 0 until 10) {
|
||||||
drawer.shadeStyle = imageFill {
|
drawer.shadeStyle = imageFill {
|
||||||
image = img
|
image = img
|
||||||
fillTransform = transform {
|
fillTransform = transform {
|
||||||
translate(0.5, 0.5)
|
translate(0.5, 0.5)
|
||||||
rotate( cos(i * 0.5 + seconds*10.0) *10.0 )
|
rotate(cos(i * 0.5 + seconds * 10.0) * 10.0)
|
||||||
scale(1.0 - i * 0.05)
|
scale(1.0 - i * 0.05)
|
||||||
translate(-0.5, -0.5)
|
translate(-0.5, -0.5)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import org.openrndr.draw.loadImage
|
|||||||
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||||
import org.openrndr.extra.shadestyles.fills.image.imageFill
|
import org.openrndr.extra.shadestyles.fills.image.imageFill
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `domainWarpFunction` in an `imageFill` shade style, used to deform
|
||||||
|
* the coordinate system of the shader. A `time` parameter is passed to the shader and used
|
||||||
|
* to alter the deformation in real time.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import org.openrndr.extra.imageFit.imageFit
|
|||||||
import org.openrndr.extra.shaderphrases.noise.simplex13
|
import org.openrndr.extra.shaderphrases.noise.simplex13
|
||||||
import org.openrndr.extra.shadestyles.fills.noise.noise
|
import org.openrndr.extra.shadestyles.fills.noise.noise
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `blueNois` variant of the `noise` shade style
|
||||||
|
* to render an image as black and white with a pointillist luma-based effect.
|
||||||
|
*
|
||||||
|
* More computationally heavy than other shade styles.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -21,17 +27,23 @@ fun main() {
|
|||||||
phase = seconds * 10.0
|
phase = seconds * 10.0
|
||||||
|
|
||||||
filterWindow = 5
|
filterWindow = 5
|
||||||
domainWarpFunction =
|
domainWarpFunction = """$simplex13
|
||||||
"""$simplex13
|
vec3 domainWarp(vec3 p) {
|
||||||
vec3 domainWarp(vec3 p) { float px = simplex13(p*0.01); float py = simplex13(p.yxz*-0.01); return p + 10.25 * vec3(px, py, 0.0); }""".trimIndent()
|
float px = simplex13(p * 0.01);
|
||||||
|
float py = simplex13(p.yxz * -0.01);
|
||||||
|
return p + 10.25 * vec3(px, py, 0.0);
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
blueNoise {
|
blueNoise {
|
||||||
bits = 17
|
bits = 17
|
||||||
bilinear()
|
bilinear()
|
||||||
}
|
}
|
||||||
|
|
||||||
blendFunction = """vec4 blend(vec4 o, float n) { float luma = dot(o.rgb, vec3(1.0/3.0));
|
blendFunction = """
|
||||||
|return vec4(vec3(smoothstep(luma+0.01, luma-0.01, n)), 1.0);
|
|vec4 blend(vec4 o, float n) {
|
||||||
|
| float luma = dot(o.rgb, vec3(1.0 / 3.0));
|
||||||
|
| return vec4(vec3(smoothstep(luma + 0.01, luma - 0.01, n)), 1.0);
|
||||||
|}""".trimMargin()
|
|}""".trimMargin()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import org.openrndr.math.Vector3
|
|||||||
import org.openrndr.math.transforms.transform
|
import org.openrndr.math.transforms.transform
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `simplex` variant of the `noise` shade style.
|
||||||
|
* It generates a gray-scale pattern, which is then colorized by using a `luma`
|
||||||
|
* `gradient` shade style.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -23,10 +28,13 @@ fun main() {
|
|||||||
drawer.shadeStyle = noise {
|
drawer.shadeStyle = noise {
|
||||||
phase = seconds * 0.01
|
phase = seconds * 0.01
|
||||||
simplex {
|
simplex {
|
||||||
|
|
||||||
}
|
}
|
||||||
domainWarpFunction =
|
domainWarpFunction = """
|
||||||
"""vec3 domainWarp(vec3 p) { float px = simplex13(p*4.0); float py = simplex13(p.yxz*-4.0); return p + 0.25 * vec3(px, py, px*py); }"""
|
vec3 domainWarp(vec3 p) {
|
||||||
|
float px = simplex13(p*4.0);
|
||||||
|
float py = simplex13(p.yxz*-4.0);
|
||||||
|
return p + 0.25 * vec3(px, py, px*py);
|
||||||
|
}""".trimIndent()
|
||||||
|
|
||||||
anisotropicFbm {
|
anisotropicFbm {
|
||||||
octaves = 10
|
octaves = 10
|
||||||
@@ -46,7 +54,6 @@ fun main() {
|
|||||||
stops[0.75] = ColorRGBa.BLACK
|
stops[0.75] = ColorRGBa.BLACK
|
||||||
stops[1.0] = ColorRGBa.PEACH_PUFF
|
stops[1.0] = ColorRGBa.PEACH_PUFF
|
||||||
luma {
|
luma {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawer.circle(drawer.bounds.center, 300.0)
|
drawer.circle(drawer.bounds.center, 300.0)
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import org.openrndr.extra.camera.Camera2D
|
|||||||
import org.openrndr.extra.imageFit.imageFit
|
import org.openrndr.extra.imageFit.imageFit
|
||||||
import org.openrndr.extra.shadestyles.fills.noise.noise
|
import org.openrndr.extra.shadestyles.fills.noise.noise
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to render a color image as black and white
|
||||||
|
* using the `whiteNoise` variant of the `noise` shade style.
|
||||||
|
*
|
||||||
|
* A custom `blendFunction` is used to control how pixel colors are
|
||||||
|
* transformed.
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
@@ -22,7 +29,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
blendFunction = """vec4 blend(vec4 o, float n) {
|
blendFunction = """vec4 blend(vec4 o, float n) {
|
||||||
| float luma = dot(o.rgb, vec3(1.0/3.0));
|
| float luma = dot(o.rgb, vec3(1.0/3.0));
|
||||||
| return vec4(vec3(smoothstep(luma+0.01, luma-0.01, n)), 1.0);
|
| return vec4(vec3(smoothstep(luma+0.05, luma-0.05, n)), 1.0);
|
||||||
|}""".trimMargin()
|
|}""".trimMargin()
|
||||||
}
|
}
|
||||||
drawer.imageFit(image, drawer.bounds)
|
drawer.imageFit(image, drawer.bounds)
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ import org.openrndr.extra.imageFit.imageFit
|
|||||||
import org.openrndr.extra.shadestyles.fills.FillUnits
|
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||||
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `checkers` variant of the `pattern` shade style.
|
||||||
|
*
|
||||||
|
* The style is used twice with different parameters: once for a background image
|
||||||
|
* and then for a text displayed on top of it.
|
||||||
|
*
|
||||||
|
* The text shade style features a `domainWarpFunction`, which is used to deform
|
||||||
|
* the coordinate system of the shade style.
|
||||||
|
*
|
||||||
|
* Try reducing the `scale` parameter to make the checkers more obvious.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -23,7 +34,7 @@ fun main() = application {
|
|||||||
backgroundColor = ColorRGBa.NAVY
|
backgroundColor = ColorRGBa.NAVY
|
||||||
foregroundColor = ColorRGBa.WHITE
|
foregroundColor = ColorRGBa.WHITE
|
||||||
patternUnits = FillUnits.WORLD
|
patternUnits = FillUnits.WORLD
|
||||||
parameter("time", seconds*0.1)
|
parameter("time", seconds * 0.1)
|
||||||
// domainWarpFunction = """vec2 patternDomainWarp(vec2 uv) { return uv + vec2(cos(uv.y * 0.1 + p_time), sin(uv.x * 0.1 + p_time)) * 30.05; }"""
|
// domainWarpFunction = """vec2 patternDomainWarp(vec2 uv) { return uv + vec2(cos(uv.y * 0.1 + p_time), sin(uv.x * 0.1 + p_time)) * 30.05; }"""
|
||||||
scale = 0.4
|
scale = 0.4
|
||||||
|
|
||||||
@@ -39,7 +50,8 @@ fun main() = application {
|
|||||||
foregroundColor = ColorRGBa.WHITE
|
foregroundColor = ColorRGBa.WHITE
|
||||||
patternUnits = FillUnits.WORLD
|
patternUnits = FillUnits.WORLD
|
||||||
parameter("time", seconds)
|
parameter("time", seconds)
|
||||||
domainWarpFunction = """vec2 patternDomainWarp(vec2 uv) { return uv + vec2(cos(uv.y * 0.1 + p_time), sin(uv.x * 0.1 + p_time)) * 30.05; }"""
|
domainWarpFunction =
|
||||||
|
"""vec2 patternDomainWarp(vec2 uv) { return uv + vec2(cos(uv.y * 0.1 + p_time), sin(uv.x * 0.1 + p_time)) * 30.05; }"""
|
||||||
scale = 0.2
|
scale = 0.2
|
||||||
checkers {
|
checkers {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import org.openrndr.extra.color.presets.PEACH_PUFF
|
|||||||
import org.openrndr.extra.shadestyles.fills.FillUnits
|
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||||
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the `xorMod2` variant of the `pattern` shade style;
|
||||||
|
* an algorithmic and intricate pattern.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
|||||||
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
import org.openrndr.extra.shadestyles.fills.patterns.pattern
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of a complex shade style made by combining an
|
||||||
|
* animated `pattern`, a `gradient` and a `clip`.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -23,7 +27,7 @@ fun main() = application {
|
|||||||
backgroundColor = ColorRGBa.DARK_GRAY
|
backgroundColor = ColorRGBa.DARK_GRAY
|
||||||
foregroundColor = ColorRGBa.PEACH_PUFF
|
foregroundColor = ColorRGBa.PEACH_PUFF
|
||||||
patternUnits = FillUnits.WORLD
|
patternUnits = FillUnits.WORLD
|
||||||
parameter("time", seconds*0.1)
|
parameter("time", seconds * 0.1)
|
||||||
scale = 0.2
|
scale = 0.2
|
||||||
crosses {
|
crosses {
|
||||||
width = 1.0
|
width = 1.0
|
||||||
@@ -35,7 +39,7 @@ fun main() = application {
|
|||||||
stops[1.0] = ColorRGBa.BLACK
|
stops[1.0] = ColorRGBa.BLACK
|
||||||
stops[0.5] = ColorRGBa.WHITE
|
stops[0.5] = ColorRGBa.WHITE
|
||||||
stops[0.0] = ColorRGBa.WHITE
|
stops[0.0] = ColorRGBa.WHITE
|
||||||
conic { }
|
conic { }
|
||||||
} + clip {
|
} + clip {
|
||||||
star {
|
star {
|
||||||
sides = 36
|
sides = 36
|
||||||
@@ -53,7 +57,6 @@ fun main() = application {
|
|||||||
// drawer.text("Patterns", 10.0, height / 2.0)
|
// drawer.text("Patterns", 10.0, height / 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,11 @@ import org.openrndr.extra.objloader.loadOBJasVertexBuffer
|
|||||||
import org.openrndr.extra.shadestyles.spatial.HemisphereLight
|
import org.openrndr.extra.shadestyles.spatial.HemisphereLight
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the [HemisphereLight] shade style, a simple shader
|
||||||
|
* that can be used for simple illumination of 3D meshes.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import org.openrndr.extra.objloader.loadOBJasVertexBuffer
|
|||||||
import org.openrndr.extra.shadestyles.spatial.visualizeNormals
|
import org.openrndr.extra.shadestyles.spatial.visualizeNormals
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates the use of the [visualizeNormals] shade style, which can help
|
||||||
|
* debug the normals of a 3D mesh.
|
||||||
|
*
|
||||||
|
*/
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
configure {
|
configure {
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ import org.openrndr.math.Vector3
|
|||||||
import org.openrndr.shape.path3D
|
import org.openrndr.shape.path3D
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to create a 3D path and attach cylinders to it at regular intervals with the correct orientation.
|
||||||
|
*
|
||||||
|
* - The path is constructed using the `path3D` builder.
|
||||||
|
* - A rectified copy is created to be able to sample it at equal-length intervals.
|
||||||
|
* - We call the `frames` method on the rectified contour to generate a list with 100 transformation matrices which
|
||||||
|
* make it possible to attach oriented 3D objects at specific locations in the curve.
|
||||||
|
* - We finally use the transformation matrices to draw cylinders along the 3D path.
|
||||||
|
*
|
||||||
|
* The orbital camera extension enables interactive 3D view manipulation.
|
||||||
|
*
|
||||||
|
* A fixed random seed is used to make sure this demo outputs a specific output. We can delete the
|
||||||
|
* `random` arguments to get a unique result each time the program runs.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
|
|||||||
@@ -5,13 +5,24 @@ import org.openrndr.color.ColorRGBa
|
|||||||
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates how to use the hobbyCurve function to render a smooth closed contour
|
||||||
|
* passing through a predefined set of points.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
program {
|
program {
|
||||||
extend {
|
extend {
|
||||||
val points = listOf(Vector2(150.0, 350.0), Vector2(325.0, 100.0), Vector2(500.0, 350.0), Vector2(325.0, 250.0))
|
val points = listOf(
|
||||||
|
Vector2(150.0, 350.0),
|
||||||
|
Vector2(325.0, 100.0),
|
||||||
|
Vector2(500.0, 350.0),
|
||||||
|
Vector2(325.0, 250.0)
|
||||||
|
)
|
||||||
|
|
||||||
drawer.stroke = ColorRGBa.BLACK
|
drawer.stroke = ColorRGBa.BLACK
|
||||||
drawer.fill = ColorRGBa.PINK
|
drawer.fill = ColorRGBa.PINK
|
||||||
drawer.contour(hobbyCurve(points, closed=true))
|
drawer.contour(hobbyCurve(points, closed = true))
|
||||||
|
|
||||||
drawer.fill = ColorRGBa.WHITE
|
drawer.fill = ColorRGBa.WHITE
|
||||||
drawer.circles(points, 4.0)
|
drawer.circles(points, 4.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
|||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This demo creates a list of random 2D points, finds the alpha shape contour for those points,
|
||||||
|
* and finally makes that contour smooth by calling `hobbyCurve()`.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
@@ -15,16 +19,17 @@ fun main() = application {
|
|||||||
program {
|
program {
|
||||||
val points = List(40) {
|
val points = List(40) {
|
||||||
Vector2(
|
Vector2(
|
||||||
Random.nextDouble(width*0.25, width*0.75),
|
Random.nextDouble(width * 0.25, width * 0.75),
|
||||||
Random.nextDouble(height*0.25, height*0.75)
|
Random.nextDouble(height * 0.25, height * 0.75)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val alphaShape = AlphaShape(points)
|
val alphaShape = AlphaShape(points)
|
||||||
val c = alphaShape.createContour()
|
val c = alphaShape.createContour()
|
||||||
val hobby = hobbyCurve(c.segments.map { it.start }, closed=true)
|
val hobby = c.hobbyCurve()
|
||||||
extend {
|
extend {
|
||||||
drawer.fill = ColorRGBa.PINK
|
drawer.fill = ColorRGBa.PINK
|
||||||
drawer.contour(hobby)
|
drawer.contour(hobby)
|
||||||
|
|
||||||
drawer.fill = ColorRGBa.WHITE
|
drawer.fill = ColorRGBa.WHITE
|
||||||
drawer.circles(points, 4.0)
|
drawer.circles(points, 4.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,28 @@ package hobbycurve
|
|||||||
|
|
||||||
import org.openrndr.application
|
import org.openrndr.application
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extra.noise.scatter
|
|
||||||
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
||||||
import org.openrndr.extra.shapes.ordering.hilbertOrder
|
import org.openrndr.extra.shapes.primitives.regularStar
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This demo shows how the [org.openrndr.shape.ShapeContour]'s method `hobbyCurve()` can be used
|
||||||
|
* to round contours with linear segments.
|
||||||
|
*/
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
configure {
|
configure {
|
||||||
width = 720
|
width = 720
|
||||||
height = 720
|
height = 720
|
||||||
}
|
}
|
||||||
program {
|
program {
|
||||||
|
val star = regularStar(5, 100.0, 300.0, drawer.bounds.center)
|
||||||
|
val hobby = star.hobbyCurve()
|
||||||
extend {
|
extend {
|
||||||
for (i in -20..20) {
|
drawer.fill = ColorRGBa.PINK
|
||||||
val t = i / 10.0
|
drawer.contour(hobby)
|
||||||
val points = drawer.bounds.offsetEdges(-50.0).scatter(25.0, random = Random(0)).hilbertOrder()
|
|
||||||
drawer.stroke = ColorRGBa.WHITE.opacify(0.5)
|
drawer.fill = null
|
||||||
drawer.fill = null
|
drawer.stroke = ColorRGBa.WHITE.opacify(0.5)
|
||||||
drawer.contour(hobbyCurve(points, closed = false, tensions = { i, inAngle, outAngle ->
|
drawer.contour(star)
|
||||||
Pair(t, t)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user