├── .gitignore
├── README.md
├── pom.xml
└── src
└── main
├── java
└── com
│ └── heledron
│ └── text_display_experiments
│ ├── TextDisplayExperimentsPlugin.kt
│ ├── bitmaps
│ ├── meshes
│ │ ├── Cube.kt
│ │ ├── RainbowTriangle.kt
│ │ └── Water.kt
│ ├── renderer3d.kt
│ ├── scenes
│ │ ├── MandelbrotSetScene.kt
│ │ ├── RainbowTriangleScene.kt
│ │ ├── RotatingCubeScene.kt
│ │ └── scenes.kt
│ ├── setupBitmapDisplay.kt
│ └── shaders
│ │ ├── RGB2DShader.kt
│ │ ├── RGBShader.kt
│ │ └── WaterShader.kt
│ ├── paint_program
│ ├── ColorPicker.kt
│ ├── HuePicker.kt
│ ├── SVPicker.kt
│ └── setupPaintProgram.kt
│ ├── particle_system
│ ├── FireFlyParticle.kt
│ ├── FlameParticle.kt
│ ├── WaterParticle.kt
│ └── setupParticleSystem.kt
│ ├── point_detector_visualizer
│ └── PointDetectorVisualizer.kt
│ ├── setupCloak.kt
│ ├── setupColoredCauldrons.kt
│ ├── setupTextEntityUtilities.kt
│ └── utilities
│ ├── Grid.kt
│ ├── Rect.kt
│ ├── blockColorLookUp.kt
│ ├── colors.kt
│ ├── core.kt
│ ├── customEntities.kt
│ ├── customItems.kt
│ ├── events.kt
│ ├── maths.kt
│ ├── oklab.kt
│ ├── overloads.kt
│ ├── pointDetection.kt
│ ├── rendering
│ ├── EntityRenderer.kt
│ └── utilities.kt
│ └── scheduler.kt
└── resources
├── block_colors.json
└── plugin.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific stuff
2 | .idea/
3 |
4 | *.iml
5 | *.ipr
6 | *.iws
7 |
8 | # IntelliJ
9 | out/
10 |
11 | # Compiled class file
12 | *.class
13 |
14 | # Log file
15 | *.log
16 |
17 | # BlueJ files
18 | *.ctxt
19 |
20 | # Package Files #
21 | *.jar
22 | *.war
23 | *.nar
24 | *.ear
25 | *.zip
26 | *.tar.gz
27 | *.rar
28 |
29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
30 | hs_err_pid*
31 |
32 | *~
33 |
34 | # temporary files which can be created if a process still has a handle open of a deleted file
35 | .fuse_hidden*
36 |
37 | # KDE directory preferences
38 | .directory
39 |
40 | # Linux trash folder which might appear on any partition or disk
41 | .Trash-*
42 |
43 | # .nfs files are created when an open file is removed but is still being accessed
44 | .nfs*
45 |
46 | # General
47 | .DS_Store
48 | .AppleDouble
49 | .LSOverride
50 |
51 | # Icon must end with two \r
52 | Icon
53 |
54 | # Thumbnails
55 | ._*
56 |
57 | # Files that might appear in the root of a volume
58 | .DocumentRevisions-V100
59 | .fseventsd
60 | .Spotlight-V100
61 | .TemporaryItems
62 | .Trashes
63 | .VolumeIcon.icns
64 | .com.apple.timemachine.donotpresent
65 |
66 | # Directories potentially created on remote AFP share
67 | .AppleDB
68 | .AppleDesktop
69 | Network Trash Folder
70 | Temporary Items
71 | .apdisk
72 |
73 | # Windows thumbnail cache files
74 | Thumbs.db
75 | Thumbs.db:encryptable
76 | ehthumbs.db
77 | ehthumbs_vista.db
78 |
79 | # Dump file
80 | *.stackdump
81 |
82 | # Folder config file
83 | [Dd]esktop.ini
84 |
85 | # Recycle Bin used on file shares
86 | $RECYCLE.BIN/
87 |
88 | # Windows Installer files
89 | *.cab
90 | *.msi
91 | *.msix
92 | *.msm
93 | *.msp
94 |
95 | # Windows shortcuts
96 | *.lnk
97 |
98 | target/
99 |
100 | pom.xml.tag
101 | pom.xml.releaseBackup
102 | pom.xml.versionsBackup
103 | pom.xml.next
104 |
105 | release.properties
106 | dependency-reduced-pom.xml
107 | buildNumber.properties
108 | .mvn/timing.properties
109 | .mvn/wrapper/maven-wrapper.jar
110 | .flattened-pom.xml
111 |
112 | # Common working directory
113 | run/
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Text Display Experiments
2 | ## Introduction
3 | This plugin showcases unusual ways to use Minecraft's text display entities.
4 |
5 | It was made as part of a video: https://youtu.be/uZmEYYs0ZKs
6 |
7 | This plugin is very experimental and untested in multiplayer. Use at your own risk.
8 |
9 |
10 |
11 | ## Installation
12 | 1. Download the JAR from the [releases page](https://github.com/TheCymaera/minecraft-text-display-experiments/releases/).
13 | 2. Set up a [Paper](https://papermc.io/downloads) or [Spigot](https://getbukkit.org/download/spigot) server. (Instructions below)
14 | 3. Add the JAR to the `plugins` folder.
15 |
16 |
17 |
18 | ## Running a Server
19 | 1. Download a server JAR from [Paper](https://papermc.io/downloads) or [Spigot](https://getbukkit.org/download/spigot).
20 | 2. Run the following command `java -Xmx1024M -Xms1024M -jar server.jar nogui`.
21 | 3. I typically use the Java runtime bundled with my Minecraft installation so as to avoid version conflicts.
22 | - In Modrinth, you can find the Java runtime location inside the profile options menu.
23 | 4. Accept the EULA by changing `eula=false` to `eula=true` in the `eula.txt` file.
24 | 5. Join the server with `localhost` as the IP address.
25 |
26 |
27 | ## Commands
28 | Autocomplete will show available options.
29 |
30 | Get control items:
31 | ```
32 | /items
33 | ```
34 |
35 | Bitmap display:
36 | ```
37 | # Create directly
38 | execute unless entity @e[tag=bitmap_display] run summon minecraft:marker ~ ~1.25 ~ {Tags:["bitmap_display"],Rotation:[90f,0f]}
39 |
40 | # Toggle with sound effects
41 | execute unless entity @e[tag=bitmap_display] run summon minecraft:marker ~ ~1.25 ~ {Tags:["pre_bitmap_display"],Rotation:[90f,0f]}
42 | kill @e[tag=bitmap_display]
43 | tag @e[tag=pre_bitmap_display] add bitmap_display
44 | execute if entity @e[tag=bitmap_display] run playsound minecraft:block.beacon.activate block @a ~ ~ ~ 1 1
45 | execute unless entity @e[tag=bitmap_display] run playsound minecraft:block.beacon.deactivate block @a ~ ~ ~ 1 1
46 | ```
47 |
48 | Paint app:
49 | ```
50 | # Create
51 | summon minecraft:marker ~ ~1 ~ {Tags:["paint_app.hue_picker", "paint_app"],Rotation:[45f,0f]}
52 | summon minecraft:marker ~ ~1 ~-1 {Tags:["paint_app.sv_picker", "paint_app"],Rotation:[45f,0f]}
53 | summon minecraft:marker ~ ~1 ~-1 {Tags:["paint_app.canvas", "paint_app"],Rotation:[0f,0f]}
54 | execute as @e[tag=paint_app.hue_picker] at @s run tp @s ^-1.3 ^ ^
55 |
56 | # Remove
57 | kill @e[tag=paint_app]
58 |
59 | # Set hue picker options
60 | data modify entity @n[tag=paint_app.hue_picker] BukkitValues."paint_app:items" set value 120
61 | data modify entity @n[tag=paint_app.hue_picker] BukkitValues."paint_app:width" set value .2f
62 | data modify entity @n[tag=paint_app.hue_picker] BukkitValues."paint_app:items" set value 2f
63 |
64 | # Set sv picker options
65 | data modify entity @n[tag=paint_app.sv_picker] BukkitValues."paint_app:items" set value 50
66 | data modify entity @n[tag=paint_app.sv_picker] BukkitValues."paint_app:width" set value 2f
67 | data modify entity @n[tag=paint_app.sv_picker] BukkitValues."paint_app:height" set value 2f
68 |
69 | # Set canvas options
70 | # (Changing the bitmap size will clear the canvas)
71 | data modify entity @n[tag=paint_app.canvas] BukkitValues."paint_app:bitmap_width" set value16
72 | data modify entity @n[tag=paint_app.canvas] BukkitValues."paint_app:bitmap_height" set value 16
73 | data modify entity @n[tag=paint_app.canvas] BukkitValues."paint_app:display_height" set value 2f
74 | ```
75 |
76 | Rainbow cycle animation:
77 | ```
78 | summon minecraft:text_display ~1 ~1 ~ {text:'"Hello World"',Rotation:[0f,0f],brightness:{sky:15,block:15},interpolation_duration:3}
79 | data modify entity @n[type=minecraft:text_display] background set value -65536
80 | data modify entity @n[type=minecraft:text_display] text set value '" "'
81 | data merge entity @n[type=minecraft:text_display] {start_interpolation:-1,transformation:{translation:[-.1f,-.5f,0f],scale:[8.0f,4.0f,1f]}}
82 | tag @n[type=minecraft:text_display] add rainbow_cycle_animation
83 | ```
84 |
85 | Pulsating animation:
86 | ```
87 | summon minecraft:area_effect_cloud ~ ~.5 ~ {Passengers:[
88 | {id:"minecraft:text_display",Rotation:[0f,0f] ,Tags:["pulsating_animation"],text:'" "',background:0,transformation:{translation:[-.1f,-.5f,.501f],scale:[8.0f,4.0f,1f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]},brightness:{sky:15,block:15}},
89 | {id:"minecraft:text_display",Rotation:[90f,0f] ,Tags:["pulsating_animation"],text:'" "',background:0,transformation:{translation:[-.1f,-.5f,.501f],scale:[8.0f,4.0f,1f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]},brightness:{sky:15,block:15}},
90 | {id:"minecraft:text_display",Rotation:[180f,0f],Tags:["pulsating_animation"],text:'" "',background:0,transformation:{translation:[-.1f,-.5f,.501f],scale:[8.0f,4.0f,1f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]},brightness:{sky:15,block:15}},
91 | {id:"minecraft:text_display",Rotation:[270f,0f],Tags:["pulsating_animation"],text:'" "',background:0,transformation:{translation:[-.1f,-.5f,.501f],scale:[8.0f,4.0f,1f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]},brightness:{sky:15,block:15}},
92 | {id:"minecraft:text_display",Rotation:[0f,-90f],Tags:["pulsating_animation"],text:'" "',background:0,transformation:{translation:[-.1f,-.5f,.501f],scale:[8.0f,4.0f,1f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]},brightness:{sky:15,block:15}}
93 | ]}
94 | ```
95 |
96 | Screen overlay:
97 | ```
98 | tag @p add screen_overlay.66FF0000
99 | tag @p add screen_overlay.6600FF00
100 | tag @p add screen_overlay.660000FF
101 | tag @p add screen_overlay.clear
102 | ```
103 |
104 | Colored liquid:
105 | ```
106 | execute align xyz run summon minecraft:interaction ~.5 ~.0 ~.5 {Tags:["colored_cauldron"],response:1b,width:1.01,height:1.005}
107 | ```
108 |
109 | Background color utilities:
110 | ```
111 | # Change the background color of a text display
112 | data modify entity @n[type=minecraft:text_display] BukkitValues."text_utilities:background" set value "55FF0000"
113 |
114 | # Change the background color of a text display with lerping
115 | data modify entity @n[type=minecraft:text_display] BukkitValues merge value {"text_utilities:background_lerp_speed": .05, "text_utilities:background": "55FF0000"}
116 | ```
117 |
118 | Flame particles (Run on repeat):
119 | ```
120 | # Orange
121 | summon minecraft:area_effect_cloud ~ ~1 ~ {BukkitValues:{"flame_particles:palette":"orange"}}
122 |
123 | # Blue to orange
124 | summon minecraft:area_effect_cloud ~ ~1 ~ {BukkitValues:{"flame_particles:palette":"blue_to_orange"}}
125 |
126 | # Black
127 | summon minecraft:area_effect_cloud ~ ~1 ~ {BukkitValues:{"flame_particles:palette":"black"}}
128 | ```
129 |
130 | Water splash particles (Run on repeat):
131 | ```
132 | # Frog Fountain
133 | summon minecraft:area_effect_cloud ~ ~2.375 ~ {BukkitValues:{"water_splash_particles:amount":7,"water_splash_particles:min_size": 0.09, "water_splash_particles:min_speed": .15, "water_splash_particles:up_angle_bias": 3}}
134 |
135 | # Waterfall
136 | summon minecraft:area_effect_cloud ~ ~2.375 ~ {BukkitValues:{"water_splash_particles:amount":10, "water_splash_particles:min_speed": .22}}
137 | ```
138 |
139 | Firefly particles (Run on repeat):
140 | ```
141 | summon minecraft:area_effect_cloud ~ ~1 ~ {BukkitValues:{"firefly_particles:amount": 3}}
142 | ```
143 |
144 |
145 | ## Development
146 | 1. Clone or download the repo.
147 | 2. Run Maven `package` to build the plugin. The resulting JAR will be in the `target` folder.
148 | 3. For convenience, set up a symlink and add the link to the server `plugins` folder.
149 | - Windows: `mklink /D newFile.jar originalFile.jar`
150 | - Mac/Linux: `ln -s originalFile.jar newFile.jar `
151 |
152 | ## License
153 | You may use the plugin and source code for both commercial or non-commercial purposes.
154 |
155 | Attribution is appreciated but not due.
156 |
157 | Do not resell without making substantial changes.
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.heledron
8 | TextDisplayExperiments
9 | 1.0-SNAPSHOT
10 | jar
11 |
12 | TextDisplayExperiments
13 |
14 |
15 | 1.8
16 | UTF-8
17 | 2.1.0
18 | true
19 |
20 |
21 |
22 |
23 |
24 | org.jetbrains.kotlin
25 | kotlin-maven-plugin
26 | ${kotlin.version}
27 |
28 |
29 | compile
30 | compile
31 |
32 | compile
33 |
34 |
35 |
36 | test-compile
37 | test-compile
38 |
39 | test-compile
40 |
41 |
42 |
43 |
44 | ${java.version}
45 |
46 |
47 |
48 | org.apache.maven.plugins
49 | maven-shade-plugin
50 | 3.5.2
51 |
52 |
53 | package
54 |
55 | shade
56 |
57 |
58 | false
59 | true
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | src/main/resources
68 | true
69 |
70 |
71 |
72 |
73 |
74 |
75 | papermc-repo
76 | https://papermc.io/repo/repository/maven-public/
77 |
78 |
79 | sonatype
80 | https://oss.sonatype.org/content/groups/public/
81 |
82 |
83 | spigot-repo
84 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | org.spigotmc
98 | spigot-api
99 | 1.21.3-R0.1-SNAPSHOT
100 | provided
101 |
102 |
103 | org.jetbrains.kotlin
104 | kotlin-stdlib-jdk8
105 | ${kotlin.version}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/TextDisplayExperimentsPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments
2 |
3 | import com.heledron.text_display_experiments.bitmaps.setupBitmapDisplay
4 | import com.heledron.text_display_experiments.paint_program.setupPaintProgram
5 | import com.heledron.text_display_experiments.particle_system.setupParticleSystem
6 | import com.heledron.text_display_experiments.point_detector_visualizer.setUpPointDetectorVisualizer
7 | import com.heledron.text_display_experiments.utilities.*
8 | import org.bukkit.entity.Player
9 | import org.bukkit.plugin.java.JavaPlugin
10 | import org.joml.*
11 |
12 | val textBackgroundTransform: Matrix4f; get() = Matrix4f()
13 | .translate(-0.1f + .5f,-0.5f + .5f,0f)
14 | .scale(8.0f,4.0f,1f) // + 0.003f + 0.001f
15 |
16 | @Suppress("unused")
17 | class TextDisplayExperimentsPlugin : JavaPlugin() {
18 | override fun onDisable() {
19 | logger.info("Disabling Text Display Experiments plugin")
20 | closeCurrentPlugin()
21 | }
22 |
23 | override fun onEnable() {
24 | logger.info("Enabling Text Display Experiments plugin")
25 |
26 | currentPlugin = this
27 |
28 | setupBitmapDisplay()
29 | setupTextEntityUtilities()
30 | setupPaintProgram()
31 | setupCloak()
32 | setupParticleSystem()
33 | setupColoredCauldrons()
34 | setUpPointDetectorVisualizer()
35 |
36 | getCommand("items")?.setExecutor { sender, _, _, _ ->
37 | openCustomItemInventory(sender as? Player ?: run {
38 | sender.sendMessage("Only players can use this command")
39 | return@setExecutor true
40 | })
41 | true
42 | }
43 |
44 | // getCommand("player_set_data")?.apply {
45 | // setExecutor { sender, _, _, args ->
46 | // val entitySelector = args.getOrNull(1) ?: run {
47 | // sender.sendMessage("No entity selector provided")
48 | // return@setExecutor true
49 | // }
50 | //
51 | // val player = entitySelector.let {
52 | // Bukkit.selectEntities(sender, it).firstOrNull() as? Player
53 | // } ?: run {
54 | // sender.sendMessage("No player found")
55 | // return@setExecutor true
56 | // }
57 | //
58 | // val key = args.getOrNull(2)?.let { NamespacedKey.fromString(it) } ?: run {
59 | // sender.sendMessage("No key provided")
60 | // return@setExecutor true
61 | // }
62 | //
63 | // val value = args.getOrNull(3) ?: run {
64 | // sender.sendMessage("No value provided")
65 | // return@setExecutor true
66 | // }
67 | //
68 | // player.persistentDataContainer.
69 | //
70 | //
71 | // sender.sendMessage("Modified player data")
72 | // true
73 | // }
74 | // }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/meshes/Cube.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.meshes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.Mesh
4 |
5 | object Cube: Mesh {
6 | override val vertices = arrayOf(
7 | doubleArrayOf(-1.0, +1.0, +1.0, 1.0, 0.0, 0.0),
8 | doubleArrayOf(-1.0, -1.0, +1.0, 1.0, 0.0, 0.0),
9 | doubleArrayOf(-1.0, -1.0, -1.0, 1.0, 0.0, 0.0),
10 | doubleArrayOf(-1.0, +1.0, -1.0, 1.0, 0.0, 0.0),
11 | doubleArrayOf(+1.0, +1.0, +1.0, 0.7, 0.2, 0.5),
12 | doubleArrayOf(+1.0, -1.0, +1.0, 0.7, 0.2, 0.5),
13 | doubleArrayOf(+1.0, -1.0, -1.0, 0.7, 0.2, 0.5),
14 | doubleArrayOf(+1.0, +1.0, -1.0, 0.7, 0.2, 0.5),
15 | doubleArrayOf(+1.0, +1.0, -1.0, 0.2, 0.2, 0.7),
16 | doubleArrayOf(+1.0, -1.0, -1.0, 0.2, 0.2, 0.7),
17 | doubleArrayOf(-1.0, -1.0, -1.0, 0.2, 0.2, 0.7),
18 | doubleArrayOf(-1.0, +1.0, -1.0, 0.2, 0.2, 0.7),
19 | doubleArrayOf(+1.0, +1.0, +1.0, 0.0, 1.0, 0.0),
20 | doubleArrayOf(+1.0, -1.0, +1.0, 0.0, 1.0, 0.0),
21 | doubleArrayOf(-1.0, -1.0, +1.0, 0.0, 1.0, 0.0),
22 | doubleArrayOf(-1.0, +1.0, +1.0, 0.0, 1.0, 0.0),
23 | doubleArrayOf(-1.0, -1.0, -1.0, 0.5, 0.5, 1.0),
24 | doubleArrayOf(-1.0, -1.0, +1.0, 0.5, 0.5, 1.0),
25 | doubleArrayOf(+1.0, -1.0, +1.0, 0.5, 0.5, 1.0),
26 | doubleArrayOf(+1.0, -1.0, -1.0, 0.5, 0.5, 1.0),
27 | doubleArrayOf(-1.0, +1.0, -1.0, 1.0, 0.7, 0.0),
28 | doubleArrayOf(-1.0, +1.0, +1.0, 1.0, 0.7, 0.0),
29 | doubleArrayOf(+1.0, +1.0, +1.0, 1.0, 0.7, 0.0),
30 | doubleArrayOf(+1.0, +1.0, -1.0, 1.0, 0.7, 0.0)
31 | )
32 |
33 | override val indices = intArrayOf(
34 | // FRONT
35 | 0, 1, 2,
36 | 0, 2, 3,
37 | // BACK
38 | 5, 4, 6,
39 | 6, 4, 7,
40 | // LEFT
41 | 8, 9, 10,
42 | 8, 10, 11,
43 | // RIGHT
44 | 13, 12, 14,
45 | 15, 14, 12,
46 | // UNDER
47 | 16, 17, 18,
48 | 16, 18, 19,
49 | // TOP
50 | 21, 20, 22,
51 | 22, 20, 23
52 | )
53 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/meshes/RainbowTriangle.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.meshes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.Mesh
4 |
5 | object RainbowTriangle : Mesh {
6 | override val vertices = arrayOf(
7 | doubleArrayOf(0.0, 0.5, 1.0, 1.0, 0.0),
8 | doubleArrayOf(-.5, -.5, 0.7, 0.0, 1.0),
9 | doubleArrayOf(0.5, -.5, 0.1, 1.0, 0.6)
10 | )
11 | override val indices = intArrayOf(0, 1, 2)
12 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/meshes/Water.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.meshes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.Mesh
4 |
5 | object Water: Mesh {
6 | override val vertices = arrayOf(
7 | doubleArrayOf(-6.0, -5.0, -6.0, 1.0, 1.0),
8 | doubleArrayOf(-6.0, -5.0, +6.0, 1.0, 0.0),
9 | doubleArrayOf(+6.0, -5.0, +6.0, 0.0, 0.0),
10 | doubleArrayOf(+6.0, -5.0, -6.0, 0.0, 1.0)
11 | )
12 | override val indices = intArrayOf(
13 | 0, 1, 2,
14 | 0, 2, 3
15 | )
16 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/renderer3d.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps
2 |
3 | import com.heledron.text_display_experiments.utilities.Grid
4 | import org.bukkit.Color
5 | import org.joml.Vector4d
6 |
7 | import kotlin.math.abs
8 |
9 | interface ShaderProgram {
10 | val vertex: VertexShader
11 | val fragment: FragmentShader
12 | }
13 |
14 | class VertexData(val position: Vector4d, val pixel: DoubleArray)
15 |
16 | class FragmentData(var color: Color, var depth: Double = Double.NaN)
17 |
18 | interface Mesh {
19 | val vertices: Array
20 | val indices: IntArray
21 | }
22 |
23 | typealias VertexShader = (vertex: DoubleArray)-> VertexData
24 |
25 | typealias FragmentShader = (pixel: DoubleArray)-> FragmentData
26 |
27 | typealias RenderBuffer = Grid
28 |
29 | fun RenderBuffer.drawTriangles(vertices: Mesh, shaders: ShaderProgram, doDepthTesting: Boolean) {
30 | val vertexData = vertices.vertices.map(shaders.vertex)
31 | for (i in vertices.indices.indices step 3) {
32 | val v1 = vertexData[vertices.indices[i]]
33 | val v2 = vertexData[vertices.indices[i + 1]]
34 | val v3 = vertexData[vertices.indices[i + 2]]
35 | drawTriangle(v1, v2, v3, shaders.fragment, doDepthTesting)
36 | }
37 | }
38 |
39 | private fun RenderBuffer.drawTriangle(v1: VertexData, v2: VertexData, v3: VertexData, pixelShader: FragmentShader, doDepthTesting: Boolean) {
40 | val pos1 = v1.position
41 | val pos2 = v2.position
42 | val pos3 = v3.position
43 | val pixel1 = v1.pixel
44 | val pixel2 = v2.pixel
45 | val pixel3 = v3.pixel
46 | val w1 = abs(1 / pos1.w)
47 | val w2 = abs(1 / pos2.w)
48 | val w3 = abs(1 / pos3.w)
49 | val x1 = pos1.x * w1
50 | val y1 = pos1.y * w1
51 | val z1 = pos1.z * w1
52 | val x2 = pos2.x * w2
53 | val y2 = pos2.y * w2
54 | val z2 = pos2.z * w2
55 | val x3 = pos3.x * w3
56 | val y3 = pos3.y * w3
57 | val z3 = pos3.z * w3
58 |
59 | // Pre-compute constants for finding barycentric coordinates
60 | val denom = 1 / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3))
61 | val l1 = y2 - y3
62 | val l2 = y3 - y1
63 | val r1 = x3 - x2
64 | val r2 = x1 - x3
65 |
66 | // Get delta x & y (clip-space length of 1 "pixel")
67 | val dx = 2.0 / width
68 | val dy = 2.0 / height
69 |
70 | // iterate through every pixel
71 | var i = 0
72 | var y = -1.0
73 | var py = 0
74 | while (py < height) {
75 | var x = -1.0
76 | var px = 0
77 | while (px < width) {
78 |
79 | // find barycentric coordinates of point
80 | val b1 = (l1 * (x - x3) + r1 * (y - y3)) * denom
81 | val b2 = (l2 * (x - x3) + r2 * (y - y3)) * denom
82 | val b3 = 1 - b1 - b2
83 |
84 | // skip if point is not inside triangle
85 | if (b1 < 0 || b2 < 0 || b3 < 0) {
86 | px++
87 | x += dx
88 | i++
89 | continue
90 | }
91 |
92 |
93 | // find pixel z & w
94 | val pz = z1 * b1 + z2 * b2 + z3 * b3
95 | val pw = w1 * b1 + w2 * b2 + w3 * b3
96 |
97 | // depth testing
98 | val depth = 1 / pz
99 | if (doDepthTesting && this[i].depth > depth) {
100 | px++
101 | x += dx
102 | i++
103 | continue
104 | }
105 |
106 | // clip near & far
107 | if (pz < -1 || pz > 1) {
108 | px++
109 | x += dx
110 | i++
111 | continue
112 | }
113 |
114 | // find the perspective-corrected barycentric coordinates
115 | val cb1 = 1 / pw * b1 * w1
116 | val cb2 = 1 / pw * b2 * w2
117 | val cb3 = 1 / pw * b3 * w3
118 |
119 | // use perspective-corrected b-coordinates to find interpolated pixel
120 | val pixel = DoubleArray(pixel1.size)
121 | for (t in pixel.indices) {
122 | pixel[t] = cb1 * pixel1[t] + cb2 * pixel2[t] + cb3 * pixel3[t]
123 | }
124 |
125 | // invoke pixel shader
126 | val pixelData = pixelShader(pixel)
127 | this[i] = pixelData
128 | if (pixelData.depth.isNaN()) pixelData.depth = depth
129 | px++
130 | x += dx
131 | i++
132 | }
133 | py++
134 | y += dy
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/scenes/MandelbrotSetScene.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.scenes
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import org.bukkit.Color
5 | import org.joml.Vector2d
6 | import kotlin.math.log2
7 |
8 | class MandelbrotSetScene: Scene {
9 | private val height = 64
10 | private val bitmap = Grid((height * 2.0).toInt(), height) { CLEAR_COLOR }
11 | override fun getBitmap() = bitmap
12 |
13 | var viewRect = Rect.fromMinMax(
14 | min = Vector2d(-2.0, .0),
15 | max = Vector2d(.5 , .0),
16 | ).apply {
17 | setYCenter(.0, this.width * (bitmap.height.toDouble() / bitmap.width))
18 | expand(.5)
19 | }
20 |
21 | var viewRectAnchor = viewRect.clone()
22 |
23 | var maxIterationsBase = 50
24 | var maxIterationsScale = 25.0
25 |
26 | var zoomLevel = 1.0
27 | private fun maxIterations() = (maxIterationsBase + log2(zoomLevel) * maxIterationsScale).toInt()
28 |
29 | var maxIterations = maxIterations()
30 |
31 | private val originalWidth = viewRect.width
32 | override fun update() {
33 | zoomLevel = originalWidth / viewRect.width
34 | maxIterations = maxIterations()
35 |
36 |
37 | for ((px, py) in bitmap.indices()) {
38 | val x = viewRect.minX + viewRect.width * (px.toDouble() / bitmap.width)
39 | val y = viewRect.minY + viewRect.height * (py.toDouble() / bitmap.height)
40 |
41 | var a = x
42 | var b = y
43 |
44 | var i = 0
45 | while (i < maxIterations) {
46 | val a2 = a * a - b * b + x
47 | val b2 = 2 * a * b + y
48 | a = a2
49 | b = b2
50 |
51 | if (Vector2d(a,b).lengthSquared() > 4) break
52 | i += 1
53 | }
54 |
55 | bitmap[px to py] = colors.interpolateRGB(i.toDouble() / maxIterations)
56 | }
57 | }
58 |
59 |
60 | private val colors = listOf(
61 | 0.0 to Color.fromRGB( 0, 7, 100),
62 | 0.16 to Color.fromRGB( 32, 107, 203),
63 | 0.42 to Color.fromRGB(237, 255, 255),
64 | 0.6425 to Color.fromRGB(255, 170, 0),
65 | 0.8575 to Color.fromRGB( 0, 0, 0),
66 | )
67 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/scenes/RainbowTriangleScene.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.scenes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.FragmentData
4 | import com.heledron.text_display_experiments.bitmaps.RenderBuffer
5 | import com.heledron.text_display_experiments.bitmaps.drawTriangles
6 | import com.heledron.text_display_experiments.bitmaps.meshes.RainbowTriangle
7 | import com.heledron.text_display_experiments.bitmaps.shaders.RGB2DShader
8 |
9 | class RainbowTriangleScene : Scene {
10 | val buffer = RenderBuffer(64, 64) { FragmentData(CLEAR_COLOR) }
11 | override fun getBitmap() = buffer.map { it.color }
12 | init {
13 | buffer.drawTriangles(RainbowTriangle, RGB2DShader, true)
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/scenes/RotatingCubeScene.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.scenes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.FragmentData
4 | import com.heledron.text_display_experiments.bitmaps.RenderBuffer
5 | import com.heledron.text_display_experiments.bitmaps.drawTriangles
6 | import com.heledron.text_display_experiments.bitmaps.meshes.Cube
7 | import com.heledron.text_display_experiments.bitmaps.meshes.Water
8 | import com.heledron.text_display_experiments.bitmaps.shaders.RGBShader
9 | import com.heledron.text_display_experiments.bitmaps.shaders.WaterShader
10 | import com.heledron.text_display_experiments.utilities.FORWARD_VECTOR
11 | import com.heledron.text_display_experiments.utilities.toRadians
12 | import org.bukkit.Color
13 | import org.joml.Matrix4d
14 | import org.joml.Quaterniond
15 | import org.joml.Vector3d
16 |
17 | private val TRANSPARENT = Color.fromARGB(0, 0, 0, 0)
18 |
19 | class RotatingCubeScene : Scene {
20 | // time
21 | var tick = 0
22 |
23 | // cube
24 | var cubeOrientation = Quaterniond()
25 | var rotateCube = true
26 | var cubeRotationPerTick = Quaterniond().apply {
27 | val amount = .3 * (2.0 * Math.PI) / 20.0
28 | rotateY(amount)
29 | rotateX(amount/4)
30 | }
31 |
32 | // buffer
33 | val buffer = RenderBuffer(64 * 2, 64) { FragmentData(TRANSPARENT) }
34 | override fun getBitmap() = buffer.map { it.color }
35 |
36 | // camera
37 | var cameraAngleX = 0.0
38 | var cameraAngleY = 30.0
39 | var cameraDistance = 15.0
40 | private var camera = Camera(Math.PI / 4, aspectRatio = buffer.width.toDouble() / buffer.height.toDouble())
41 | private var reflectionCamera = Camera(Math.PI / 2.2).apply { lookAt(
42 | Vector3d(0.0, -5.0, 0.0), // camera position (at water surface)
43 | Vector3d(0.0, 0.0, 0.0), // cube position
44 | Vector3d(0.0, 0.0, -1.0) // up vector
45 | ) }
46 |
47 | // water
48 | val reflectionTexture = RenderBuffer(64, 64) { FragmentData(CLEAR_COLOR) }
49 | var drawWater = true
50 |
51 |
52 | override fun update() {
53 | tick++
54 |
55 | // clear buffer
56 | buffer.setAll { FragmentData(CLEAR_COLOR) }
57 |
58 | // update camera position
59 | val cameraRotation = Quaterniond().rotationYXZ(cameraAngleY.toRadians(), cameraAngleX.toRadians(), 0.0)
60 | val cameraPosition = FORWARD_VECTOR.toVector3d().mul(cameraDistance).rotate(cameraRotation)
61 | camera.lookAt(cameraPosition, Vector3d(), Vector3d(0.0, 1.0, 0.0))
62 |
63 | // rotate cube
64 | if (rotateCube) cubeOrientation.premul(cubeRotationPerTick)
65 |
66 | // get cube transform
67 | val cubeTransform = Matrix4d().rotate(cubeOrientation)
68 |
69 | // draw cube
70 | RGBShader.transform = camera.getTransform(cubeTransform)
71 | buffer.drawTriangles(Cube, RGBShader, true)
72 |
73 | if (drawWater) {
74 | // render reflection
75 | reflectionTexture.setAll { FragmentData(TRANSPARENT) }
76 | RGBShader.transform = reflectionCamera.getTransform(cubeTransform)
77 | reflectionTexture.drawTriangles(Cube, RGBShader, true)
78 |
79 | // draw water
80 | WaterShader.reflectionTexture = reflectionTexture
81 | WaterShader.transform = camera.getTransform()
82 | WaterShader.distortionFrame = tick / 20.0
83 | buffer.drawTriangles(Water, WaterShader, true)
84 | }
85 | }
86 | }
87 |
88 | private class Camera(foxy: Double, aspectRatio: Double = 1.0) {
89 | val view = Matrix4d()
90 | val projection = Matrix4d().perspective(foxy, aspectRatio, 0.1, 1000.0)
91 |
92 | fun getTransform(model: Matrix4d = Matrix4d()): Matrix4d {
93 | return Matrix4d(projection).mul(view).mul(model)
94 | }
95 |
96 | fun lookAt(position: Vector3d, target: Vector3d, up: Vector3d) {
97 | this.view.setLookAt(position, target, up)
98 | }
99 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/scenes/scenes.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.scenes
2 |
3 | import com.heledron.text_display_experiments.bitmaps.FragmentData
4 | import com.heledron.text_display_experiments.bitmaps.RenderBuffer
5 | import com.heledron.text_display_experiments.utilities.Grid
6 | import org.bukkit.Color
7 |
8 | val CLEAR_COLOR = Color.fromARGB(50, 0, 0, 0)
9 |
10 | interface Scene {
11 | fun getBitmap(): Grid
12 | fun update() {}
13 | }
14 |
15 | class EmptyScene : Scene {
16 | val buffer = RenderBuffer(64, 64) { FragmentData(CLEAR_COLOR) }
17 | override fun getBitmap() = buffer.map { it.color }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/setupBitmapDisplay.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps
2 |
3 | import com.heledron.text_display_experiments.bitmaps.scenes.MandelbrotSetScene
4 | import com.heledron.text_display_experiments.bitmaps.scenes.RainbowTriangleScene
5 | import com.heledron.text_display_experiments.bitmaps.scenes.RotatingCubeScene
6 | import com.heledron.text_display_experiments.bitmaps.scenes.Scene
7 | import com.heledron.text_display_experiments.textBackgroundTransform
8 | import com.heledron.text_display_experiments.utilities.*
9 | import com.heledron.text_display_experiments.utilities.rendering.RenderEntityGroup
10 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
11 | import com.heledron.text_display_experiments.utilities.rendering.interpolateTransform
12 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
13 | import org.bukkit.Color
14 | import org.bukkit.NamespacedKey
15 | import org.bukkit.Sound
16 | import org.bukkit.World
17 | import org.bukkit.entity.Display.Brightness
18 | import org.bukkit.entity.Player
19 | import org.bukkit.util.Vector
20 | import org.joml.*
21 |
22 | @Suppress("UnstableApiUsage")
23 | fun setupBitmapDisplay() {
24 | var bitmapScene: Scene = MandelbrotSetScene()
25 |
26 | // Player lock system
27 | fun Player.lockMovement() {
28 | if (vehicle != null) {
29 | if (vehicle?.scoreboardTags?.contains("player_lock") == true) {
30 | vehicle?.scoreboardTags?.add("player_lock_active")
31 | }
32 | return
33 | }
34 |
35 | world.spawn(eyeLocation.add(.0,-1.02,.0), org.bukkit.entity.ArmorStand::class.java) {
36 | it.setGravity(false)
37 | it.isInvisible = true
38 | it.isInvulnerable = true
39 | it.isSilent = true
40 | it.isCollidable = false
41 | it.isMarker = true
42 | it.scoreboardTags.add("player_lock")
43 | it.scoreboardTags.add("player_lock_active")
44 | it.addPassenger(this)
45 | }
46 | }
47 |
48 | onTick {
49 | for (entity in EntityTag("player_lock").getEntities()) {
50 | if (!entity.scoreboardTags.contains("player_lock_active") || entity.passengers.isEmpty()) {
51 | entity.remove()
52 | continue
53 | }
54 |
55 | entity.scoreboardTags.remove("player_lock_active")
56 | }
57 | }
58 |
59 |
60 | // Change scene item
61 | val changeScene = CustomItemComponent("change_scene")
62 | customItemRegistry += createNamedItem(org.bukkit.Material.CLOCK, "Change Scene").attach(changeScene)
63 |
64 | changeScene.onGestureUse { player, _ ->
65 | bitmapScene = when (bitmapScene) {
66 | is RainbowTriangleScene -> RotatingCubeScene()
67 | is RotatingCubeScene -> MandelbrotSetScene()
68 | else -> RainbowTriangleScene()
69 | }
70 |
71 | playSound(player.location, Sound.BLOCK_DISPENSER_FAIL, 1.0f, 2.0f)
72 | }
73 |
74 | // Move 3D renderer camera
75 | val cameraMoveSpeed = .25
76 | val cameraRotateSpeed = 2
77 |
78 | fun normalizeCamera() {
79 | val rotatingCube = bitmapScene as? RotatingCubeScene ?: return
80 | if (rotatingCube.cameraAngleY > 360) rotatingCube.cameraAngleY -= 360.0
81 | rotatingCube.cameraAngleX = rotatingCube.cameraAngleX.coerceIn(-90.0, 90.0)
82 | rotatingCube.cameraDistance = rotatingCube.cameraDistance.coerceIn(3.0, 100.0)
83 | }
84 |
85 | // Orbit Camera
86 | val orbitCamera = CustomItemComponent("orbit_camera")
87 | customItemRegistry += createNamedItem(org.bukkit.Material.BLAZE_ROD, "Orbit Camera").attach(orbitCamera)
88 | orbitCamera.onHeldTick { player, _ ->
89 | val rotatingCube = bitmapScene as? RotatingCubeScene ?: return@onHeldTick
90 | player.lockMovement()
91 |
92 | val input = player.currentInput
93 | if (input.isLeft) rotatingCube.cameraAngleY -= cameraRotateSpeed
94 | if (input.isRight) rotatingCube.cameraAngleY += cameraRotateSpeed
95 | if (input.isForward) rotatingCube.cameraAngleX -= cameraRotateSpeed
96 | if (input.isBackward) rotatingCube.cameraAngleX += cameraRotateSpeed
97 | normalizeCamera()
98 | }
99 |
100 | // Move Camera
101 | val moveCamera = CustomItemComponent("move_camera")
102 | customItemRegistry += createNamedItem(org.bukkit.Material.BREEZE_ROD, "Move Camera").attach(moveCamera)
103 | moveCamera.onHeldTick { player, _ ->
104 | val rotatingCube = bitmapScene as? RotatingCubeScene ?: return@onHeldTick
105 | player.lockMovement()
106 |
107 | val input = player.currentInput
108 | if (input.isLeft) rotatingCube.cameraAngleY -= cameraRotateSpeed
109 | if (input.isRight) rotatingCube.cameraAngleY += cameraRotateSpeed
110 | if (input.isForward) rotatingCube.cameraDistance -= cameraMoveSpeed
111 | if (input.isBackward) rotatingCube.cameraDistance += cameraMoveSpeed
112 | normalizeCamera()
113 | }
114 |
115 | // Mandelbrot Zoom
116 | var cursor: Pair? = null
117 | var worldSpaceCursor: Vector2d? = null
118 | var moveTowardsCursor = false
119 | onTick {
120 | worldSpaceCursor = null
121 | moveTowardsCursor = false
122 | }
123 |
124 | fun handleCursor(player: Player) {
125 | val mandelbrotScene = bitmapScene as? MandelbrotSetScene ?: return
126 | val (x,y) = cursor ?:return
127 |
128 | val bitmap = bitmapScene.getBitmap()
129 | val wsCursor = Vector2d(
130 | mandelbrotScene.viewRect.minX + mandelbrotScene.viewRect.width * (x.toDouble() / bitmap.width),
131 | mandelbrotScene.viewRect.minY + mandelbrotScene.viewRect.height * (y.toDouble() / bitmap.height)
132 | )
133 | worldSpaceCursor = wsCursor
134 |
135 | val input = player.currentInput
136 | if (input.isForward) mandelbrotScene.viewRectAnchor.zoom(wsCursor, 1 - .05)
137 | if (input.isBackward) mandelbrotScene.viewRectAnchor.zoom(wsCursor, 1 + .05)
138 |
139 | player.sendActionBar(String.format("maxIterations: %d | zoomLevel: %.2f", mandelbrotScene.maxIterations, mandelbrotScene.zoomLevel))
140 | }
141 |
142 | val mandelbrotZoom = CustomItemComponent("mandelbrot_zoom")
143 | customItemRegistry += createNamedItem(org.bukkit.Material.SPECTRAL_ARROW, "Mandelbrot Zoom").attach(mandelbrotZoom)
144 | mandelbrotZoom.onHeldTick { player, _ ->
145 | player.lockMovement()
146 | moveTowardsCursor = true
147 | handleCursor(player)
148 | }
149 |
150 | val mandelbrotZoomVar = CustomItemComponent("mandelbrot_zoom_var")
151 | customItemRegistry += createNamedItem(org.bukkit.Material.ARROW, "Mandelbrot Zoom").attach(mandelbrotZoomVar)
152 | mandelbrotZoomVar.onHeldTick { player, _ ->
153 | player.lockMovement()
154 | handleCursor(player)
155 | }
156 |
157 | onTick {
158 | val mandelbrotScene = bitmapScene as? MandelbrotSetScene ?: return@onTick
159 | mandelbrotScene.viewRect.lerp(mandelbrotScene.viewRectAnchor, .4)
160 |
161 | if (!moveTowardsCursor) return@onTick
162 |
163 | val newRect = Rect.fromCenter(worldSpaceCursor ?: return@onTick, mandelbrotScene.viewRectAnchor.dimensions)
164 | mandelbrotScene.viewRect.lerp(newRect, .15)
165 | }
166 |
167 | // Display
168 | onTick {
169 | val entities = EntityTag("bitmap_display").getEntities().take(1)
170 |
171 | if (entities.isNotEmpty()) {
172 | bitmapScene.update()
173 | }
174 |
175 | // modify scene settings
176 |
177 | val currentCursor = cursor
178 | cursor = null
179 | for (entity in entities) {
180 | (bitmapScene as? MandelbrotSetScene)?.let { mandelbrotScene ->
181 | entity.persistentDataContainer.getInt(NamespacedKey.fromString("bitmap_display:max_iterations_base")!!)?.let {
182 | mandelbrotScene.maxIterationsBase = it
183 | }
184 | entity.persistentDataContainer.getDouble(NamespacedKey.fromString("bitmap_display:max_iterations_scale")!!)?.let {
185 | mandelbrotScene.maxIterationsScale = it
186 | }
187 | }
188 |
189 | SharedEntityRenderer.render("bitmap" to entity, bitmapToRenderEntities(
190 | world = entity.world,
191 | position = entity.location.toVector(),
192 | quaternion = entity.location.getQuaternion(),
193 | bitmap = bitmapScene.getBitmap(),
194 | cursor = if (worldSpaceCursor != null) currentCursor else null,
195 | players = entity.world.players,
196 | onHover = { _, newCursor ->
197 | cursor = newCursor
198 | }
199 | ))
200 | }
201 | }
202 | }
203 |
204 | private fun Rect.zoom(point: Vector2d, scale: Double): Rect {
205 | val fraction = 1.0 - scale
206 | minX = minX.lerp(point.x, fraction)
207 | maxX = maxX.lerp(point.x, fraction)
208 | minY = minY.lerp(point.y, fraction)
209 | maxY = maxY.lerp(point.y, fraction)
210 | return this
211 | }
212 |
213 |
214 | private fun bitmapToRenderEntities(
215 | world: World,
216 | position: Vector,
217 | quaternion: Quaternionf,
218 | players: List,
219 | bitmap: Grid,
220 | cursor: Pair?,
221 | onHover: (Player, Pair) -> Unit = { _, _ -> },
222 | ): RenderEntityGroup {
223 | val pointDetector = PlanePointDetector(players, position)
224 |
225 | val group = RenderEntityGroup()
226 |
227 | val scale = 1.0f / bitmap.height
228 | val rotPerStride = -.5f / bitmap.width
229 |
230 | val currentOffset = Vector3f()
231 | val currentRotation = Quaternionf().rotateY(rotPerStride * -bitmap.width / 2)
232 |
233 | val strideRotation = Quaternionf().rotateY(rotPerStride)
234 | val stride = RIGHT_VECTOR.multiply(scale).toVector3f().rotate(currentRotation)
235 |
236 | // calculate width so we can offset it by half
237 | val widthStride = Vector3f(stride)
238 | for (x in 0 until bitmap.width) {
239 | widthStride.rotate(strideRotation)
240 | currentOffset.sub(widthStride)
241 | }
242 | currentOffset.mul(.5f)
243 |
244 |
245 | for (x in 0 until bitmap.width) {
246 | stride.rotate(strideRotation)
247 | currentOffset.add(stride)
248 | currentRotation.premul(strideRotation)
249 |
250 | val offset = Vector3f(currentOffset)
251 | val rotation = Quaternionf(currentRotation)
252 |
253 | for (y in 0 until bitmap.height) {
254 | val transform = Matrix4f()
255 | .rotate(quaternion)
256 | .translate(offset.x, offset.y + scale * y, offset.z)
257 | .rotate(rotation)
258 | .scale(scale)
259 |
260 | pointDetector
261 | .lookingAt(transform)
262 | .forEach { player -> onHover(player, x to y) }
263 |
264 | group.add(x to y, textRenderEntity(
265 | world = world,
266 | position = position,
267 | init = {
268 | it.text = " "
269 | it.brightness = Brightness(15, 15)
270 | },
271 | update = {
272 | it.setTransformationMatrix(Matrix4f(transform).mul(textBackgroundTransform))
273 | it.backgroundColor = bitmap[x to y]
274 | }
275 | ))
276 |
277 | if (cursor != null && cursor.first == x && cursor.second == y) {
278 | val cursorTransform = Matrix4f()
279 | .rotate(quaternion)
280 | .translate(offset.x, offset.y + scale * y, offset.z)
281 | .rotate(rotation)
282 | .scale(.03f)
283 | .translate(.5f, .5f, .1f)
284 | .rotateZ(Math.PI.toFloat() / 4)
285 | .translate(-.5f, -.5f, .1f)
286 | .mul(textBackgroundTransform)
287 |
288 | group.add("cursor", textRenderEntity(
289 | world = world,
290 | position = position,
291 | init = {
292 | it.text = " "
293 | it.brightness = Brightness(15, 15)
294 | it.interpolationDuration = 1
295 | },
296 | update = {
297 | it.interpolateTransform(cursorTransform)
298 | it.backgroundColor = Color.WHITE
299 | }
300 | ))
301 | }
302 | }
303 | }
304 |
305 | return group
306 | }
307 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/shaders/RGB2DShader.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.shaders
2 |
3 | import com.heledron.text_display_experiments.bitmaps.*
4 | import org.bukkit.Color
5 | import org.joml.Vector4d
6 |
7 |
8 | object RGB2DShader : ShaderProgram {
9 | override val vertex: VertexShader = fun (vertex: DoubleArray): VertexData {
10 | if (vertex.size != 5) throw java.lang.Error("RGBAVertex shader expects exactly 5 elements")
11 | val x = vertex[0];
12 | val y = vertex[1];
13 | val r = vertex[2];
14 | val g = vertex[3];
15 | val b = vertex[4];
16 |
17 | val position = Vector4d(x,y, 0.0, 1.0)
18 | val pixel = doubleArrayOf(r,g,b)
19 | return VertexData(position, pixel)
20 | }
21 |
22 | override val fragment: FragmentShader = fun (pixel: DoubleArray): FragmentData {
23 | val r = pixel[0] * 255
24 | val g = pixel[1] * 255
25 | val b = pixel[2] * 255
26 |
27 | return FragmentData(Color.fromARGB(255, r.toInt(), g.toInt(), b.toInt()))
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/shaders/RGBShader.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.shaders
2 |
3 | import com.heledron.text_display_experiments.bitmaps.*
4 | import org.bukkit.Color
5 | import org.joml.Matrix4d
6 | import org.joml.Vector4d
7 |
8 | object RGBShader: ShaderProgram {
9 | var transform = Matrix4d()
10 |
11 | override val vertex: VertexShader = fun(vertex: DoubleArray): VertexData {
12 | if (vertex.size != 6) throw Error("RGBAVertex shader expects exactly 6 elements")
13 | val x = vertex[0];
14 | val y = vertex[1];
15 | val z = vertex[2];
16 | val r = vertex[3];
17 | val g = vertex[4];
18 | val b = vertex[5];
19 |
20 | val position = transform.transform(Vector4d(x, y, z, 1.0))
21 |
22 | val pixel = doubleArrayOf(r, g, b)
23 | return VertexData(position, pixel)
24 | }
25 |
26 | override val fragment: FragmentShader = fun(pixel: DoubleArray): FragmentData {
27 | val r = pixel[0] * 255
28 | val g = pixel[1] * 255
29 | val b = pixel[2] * 255
30 |
31 | return FragmentData(Color.fromARGB(255, r.toInt(), g.toInt(), b.toInt()))
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/bitmaps/shaders/WaterShader.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.bitmaps.shaders
2 |
3 | import com.heledron.text_display_experiments.bitmaps.*
4 | import com.heledron.text_display_experiments.utilities.Grid
5 | import com.heledron.text_display_experiments.utilities.blendAlpha
6 | import org.bukkit.Color
7 | import org.joml.Matrix4d
8 | import org.joml.Vector4d
9 | import kotlin.math.sin
10 |
11 | object WaterShader: ShaderProgram {
12 | var emptyReflectionTexture = Grid(1, 1) { FragmentData(Color.fromARGB(0, 0, 0, 0)) }
13 |
14 | var transform = Matrix4d()
15 | var reflectionTexture = emptyReflectionTexture
16 | var distortionFrame = 0.0
17 | var distortionMagnitude = 0.03
18 | var distortionRate = 2.0
19 | var distortionPeriods = 100.0
20 | var baseColor = Color.fromARGB((0.7 * 255).toInt(), (0.3 * 255).toInt(), (0.5 * 255).toInt(), (0.7 * 255).toInt())
21 | var tintColor = baseColor.setAlpha((255 * .3).toInt())
22 |
23 | override val vertex: VertexShader = fun (vertex): VertexData {
24 | if (vertex.size != 5) throw java.lang.Error("WaterVertex shader expects exactly 5 elements")
25 | val x: Double = vertex[0]
26 | val y: Double = vertex[1]
27 | val z: Double = vertex[2]
28 | val u: Double = vertex[3]
29 | val v: Double = vertex[4]
30 |
31 | val position = transform.transform(Vector4d(x,y,z,1.0))
32 | val pixel = doubleArrayOf(u, v)
33 | return VertexData(position, pixel)
34 | }
35 |
36 | override val fragment: FragmentShader = fun (pixel): FragmentData {
37 | val u: Double = pixel[0]
38 | val v: Double = pixel[1]
39 |
40 | // wave distortion effect
41 | val distortion = distortionMagnitude *
42 | sin(distortionRate * distortionFrame) *
43 | sin(v * distortionPeriods)
44 |
45 | // sample from reflection texture
46 | val reflection = reflectionTexture.getFraction(u + distortion, v).color
47 |
48 | // add tint to sample
49 | val fragColor = (if (reflection.alpha == 0) baseColor else reflection.setAlpha(baseColor.alpha)).blendAlpha(tintColor)
50 | return FragmentData(fragColor)
51 | }
52 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/paint_program/ColorPicker.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.paint_program
2 |
3 | import com.heledron.text_display_experiments.utilities.hsv
4 | import com.heledron.text_display_experiments.utilities.toHSV
5 | import org.bukkit.Color
6 |
7 | object ColorPicker {
8 | private var selectedPrivate: Color = Color.fromRGB(255, 0, 0)
9 |
10 | var selected: Color
11 | get() = selectedPrivate
12 | set(color) {
13 | selectedPrivate = color
14 |
15 | val (h,s,v) = color.toHSV()
16 | HuePicker.hue = h.toInt()
17 | SVPicker.sv = s to v
18 | }
19 |
20 |
21 | init {
22 | fun updateColor() {
23 | selectedPrivate = hsv(HuePicker.hue.toDouble(), SVPicker.sv.first, SVPicker.sv.second)
24 | }
25 |
26 | HuePicker.onPick = ::updateColor
27 | SVPicker.onPick = ::updateColor
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/paint_program/HuePicker.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.paint_program
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.*
6 | import org.bukkit.*
7 | import org.bukkit.entity.Display
8 | import org.bukkit.entity.Player
9 | import org.bukkit.util.Vector
10 | import org.joml.Matrix4f
11 | import org.joml.Quaternionf
12 | import kotlin.math.absoluteValue
13 |
14 | object HuePicker {
15 | var hue = 0
16 | var onPick: () -> Unit = {}
17 |
18 | init {
19 | onTick {
20 | val entities = EntityTag("paint_app.hue_picker").getEntities().take(1)
21 |
22 | for (entity in entities) SharedEntityRenderer.render(entity to HuePicker, hueSelector(
23 | world = entity.world,
24 | position = entity.location.toVector(),
25 | rotation = entity.location.getQuaternion(),
26 |
27 | selectedHue = hue,
28 |
29 | width = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:width")!!) ?: .2f,
30 | height = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:height")!!) ?: 2f,
31 | items = entity.persistentDataContainer.getInt(NamespacedKey.fromString("paint_app:items")!!) ?: (360 / 3),
32 |
33 | players = entity.world.players,
34 | onClick = { _, hue ->
35 | this.hue = hue
36 | onPick()
37 | playSound(entity.location, Sound.BLOCK_DISPENSER_FAIL, 1.0f, 2.0f)
38 | }
39 | ))
40 | }
41 | }
42 | }
43 |
44 |
45 | @Suppress("SameParameterValue")
46 | private fun hueSelector(
47 | world: World,
48 | position: Vector,
49 | rotation: Quaternionf,
50 |
51 | width: Float,
52 | height: Float,
53 | items: Int,
54 |
55 | selectedHue: Int,
56 |
57 | players: List,
58 | onClick: (Player, Int) -> Unit,
59 | ): RenderEntityGroup {
60 | val hoverScale = .3f
61 |
62 | val selectionBorderTranslate = .002f
63 | val selectionColorTranslate = .003f
64 |
65 | val selectionBorder = .025f
66 | val selectionHeight = .05f
67 |
68 | var hovered: Double? = null
69 | fun closeness(hue: Double): Float {
70 | val range = 30
71 | val target = hovered ?: return 0f
72 | val t = (1 - (hue - target).absoluteValue.toFloat() / range).coerceIn(0f, 1f)
73 | return t * t * (3.0f - 2.0f * t)
74 | }
75 |
76 | fun Matrix4f.translateHue(hue: Double) = translate(0f, hue.toFloat() * height / 360, 0f)
77 |
78 | val group = RenderEntityGroup()
79 | val pointDetector = PlanePointDetector(players, position)
80 |
81 | for (i in 0 until items) {
82 | val hue = i * (360.0 / items)
83 |
84 | fun planeTransform(scale: Float) = Matrix4f()
85 | .rotate(rotation)
86 | .translateHue(hue)
87 | .scale(width * scale, height / items, 1f)
88 | .translate(-.5f, 0f, 0f)
89 |
90 | pointDetector.detectClick(planeTransform(1f)).forEach { result ->
91 | hovered = hue
92 | if (result.isClicked) onClick(result.player, hue.toInt())
93 | }
94 |
95 | group.add(hue, textRenderEntity(
96 | world = world,
97 | position = position,
98 | init = {
99 | it.text = " "
100 | it.backgroundColor = hsv(hue, 1.0, 1.0)
101 | it.brightness = Display.Brightness(15, 15)
102 | it.interpolationDuration = 1
103 | },
104 | update = {
105 | val closeness = closeness(hue)
106 | val scaled = 1.0f + closeness * hoverScale
107 | it.interpolateTransform(planeTransform(scaled).mul(textBackgroundTransform))
108 | }
109 | ))
110 | }
111 |
112 | val closeness = closeness(selectedHue.toDouble())
113 | val scaled = (1.0f + closeness * hoverScale)
114 |
115 | fun cursorTransform(cursorWidth: Float, cursorHeight: Float, zTranslate: Float) = Matrix4f()
116 | .rotate(rotation)
117 | .translateHue(selectedHue.toDouble())
118 | .translate(0f, height / items / 2, 0f)
119 | .scale(cursorWidth * scaled, cursorHeight, 1f)
120 | .translate(-.5f, -.5f, zTranslate)
121 |
122 | group.add("selection_border", textRenderEntity(
123 | world = world,
124 | position = position,
125 | init = {
126 | it.text = " "
127 | it.backgroundColor = Color.WHITE
128 | it.brightness = Display.Brightness(15, 15)
129 | it.interpolationDuration = 1
130 | },
131 | update = {
132 | val transform = cursorTransform(width + selectionBorder, selectionHeight + selectionBorder, selectionBorderTranslate)
133 | it.interpolateTransform(transform.mul(textBackgroundTransform))
134 | }
135 | ))
136 |
137 | group.add("selection_color", textRenderEntity(
138 | world = world,
139 | position = position,
140 | init = {
141 | it.text = " "
142 | it.brightness = Display.Brightness(15, 15)
143 | it.interpolationDuration = 1
144 | },
145 | update = {
146 | val transform = cursorTransform(width, selectionHeight, selectionColorTranslate)
147 | it.interpolateTransform(transform.mul(textBackgroundTransform))
148 | it.backgroundColor = hsv(selectedHue.toDouble(), 1.0, 1.0)
149 | }
150 | ))
151 |
152 | return group
153 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/paint_program/SVPicker.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.paint_program
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.*
6 | import org.bukkit.*
7 | import org.bukkit.entity.Display
8 | import org.bukkit.entity.Player
9 | import org.bukkit.util.Vector
10 | import org.joml.Matrix4f
11 | import org.joml.Quaternionf
12 |
13 | object SVPicker {
14 | var sv = 1.0 to 1.0
15 | var onPick: () -> Unit = {}
16 |
17 | init {
18 | onTick {
19 | val entities = EntityTag("paint_app.sv_picker").getEntities().take(1)
20 | for (entity in entities) SharedEntityRenderer.render(entity to SVPicker, svSelector(
21 | world = entity.world,
22 | position = entity.location.toVector(),
23 | quaternion = entity.location.getQuaternion(),
24 |
25 | hue = HuePicker.hue,
26 | selectedSV = sv,
27 |
28 | width = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:width")!!) ?: 2f,
29 | height = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:height")!!) ?: 2f,
30 | items = entity.persistentDataContainer.getInt(NamespacedKey.fromString("paint_app:items")!!) ?: 50,
31 |
32 | players = entity.world.players,
33 | onClick = { _, sv ->
34 | this.sv = sv
35 | onPick()
36 | playSound(entity.location, Sound.BLOCK_DISPENSER_FAIL, 1.0f, 2.0f)
37 | }
38 | ))
39 | }
40 | }
41 | }
42 |
43 | @Suppress("SameParameterValue")
44 | private fun svSelector(
45 | world: World,
46 | position: Vector,
47 | quaternion: Quaternionf,
48 |
49 | width: Float,
50 | height: Float,
51 | items: Int,
52 |
53 | hue: Int,
54 | selectedSV: Pair,
55 |
56 | players: List,
57 | onClick: (Player, Pair) -> Unit,
58 | ): RenderEntityGroup {
59 | val cursorBorder = .025f
60 | val cursorSize = .07f
61 |
62 | var hovered: Pair? = null
63 |
64 | val group = RenderEntityGroup()
65 | val pointDetector = PlanePointDetector(players, position)
66 |
67 | val widthOffBy1 = width - (width / items)
68 | val heightOffBy1 = height - (height / items)
69 |
70 | fun Matrix4f.translateSV(sv: Pair): Matrix4f {
71 | val (s,v) = sv
72 | return translate(s.toFloat() * widthOffBy1 - width / 2, v.toFloat() * heightOffBy1, 0f)
73 | }
74 |
75 | val widthPerItem = widthOffBy1 / (items - 1)
76 | val heightPerItem = heightOffBy1 / (items - 1)
77 |
78 | fun step() = (0..< items).map { it.toDouble() / (items - 1) }
79 |
80 | for (s in step()) for (v in step()) {
81 | val sv = s to v
82 |
83 | val transform = Matrix4f()
84 | .rotate(quaternion)
85 | .translateSV(sv)
86 | .scale(widthPerItem, heightPerItem, 1f)
87 |
88 | pointDetector.detectClick(transform).forEach { result ->
89 | hovered = sv
90 | if (result.isClicked) onClick(result.player, sv)
91 | }
92 |
93 | group.add(sv, textRenderEntity(
94 | world = world,
95 | position = position,
96 | init = {
97 | it.text = " "
98 | it.brightness = Display.Brightness(15, 15)
99 | it.interpolationDuration = 1
100 | },
101 | update = {
102 | it.backgroundColor = hsv(hue.toDouble(), s, v)
103 | it.interpolateTransform(
104 | Matrix4f(transform).mul(textBackgroundTransform))
105 | }
106 | ))
107 | }
108 |
109 |
110 | fun cursor(
111 | sv: Pair,
112 | borderColor: Color,
113 | extraTranslate: Float
114 | ): RenderEntityGroup {
115 | fun transform(size: Float, zTranslate: Float) = Matrix4f()
116 | .rotate(quaternion)
117 | .translate(widthPerItem / 2, heightPerItem / 2, 0f)
118 | .translateSV(sv)
119 | .scale(size, size, 1f)
120 | .rotateZ(Math.PI.toFloat() / 4)
121 | .translate(-.5f, -.5f, zTranslate)
122 |
123 |
124 | val transform = transform(cursorSize, .001f + extraTranslate)
125 | val borderTransform = transform(cursorSize + cursorBorder, extraTranslate)
126 |
127 | val cursorGroup = RenderEntityGroup()
128 |
129 | cursorGroup.add("border", textRenderEntity(
130 | world = world,
131 | position = position,
132 | init = {
133 | it.text = " "
134 | it.backgroundColor = borderColor
135 | it.brightness = Display.Brightness(15, 15)
136 | it.interpolationDuration = 1
137 | },
138 | update = {
139 | it.interpolateTransform(borderTransform.mul(textBackgroundTransform))
140 | }
141 | ))
142 |
143 | cursorGroup.add("color", textRenderEntity(
144 | world = world,
145 | position = position,
146 | init = {
147 | it.text = " "
148 | it.brightness = Display.Brightness(15, 15)
149 | it.interpolationDuration = 1
150 | },
151 | update = {
152 | it.backgroundColor = hsv(hue.toDouble(), sv.first, sv.second)
153 | it.interpolateTransform(Matrix4f(transform).mul(textBackgroundTransform))
154 | }
155 | ))
156 |
157 | return cursorGroup
158 | }
159 |
160 | group.add("selection", cursor(
161 | sv = selectedSV,
162 | borderColor = Color.WHITE,
163 | extraTranslate = 0.002f
164 | ))
165 |
166 | val cursor = hovered
167 | if (cursor != null) group.add("cursor", cursor(
168 | sv = cursor,
169 | borderColor = Color.BLACK,
170 | extraTranslate = 0.001f
171 | ))
172 |
173 | return group
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/paint_program/setupPaintProgram.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.paint_program
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.*
6 | import org.bukkit.*
7 | import org.bukkit.entity.Display
8 | import org.bukkit.entity.Interaction
9 | import org.bukkit.entity.Player
10 | import org.bukkit.util.Vector
11 | import org.joml.Matrix4f
12 | import org.joml.Quaternionf
13 | import kotlin.random.Random
14 |
15 |
16 | fun setupPaintProgram() {
17 | // initialize color picker
18 | ColorPicker
19 |
20 | // state
21 | var bitmap = Grid(16, 16) { Color.WHITE }
22 | var hovered: Pair? = null
23 |
24 | // custom items
25 | val brush = CustomItemComponent("paint_app_brush")
26 | customItemRegistry += createNamedItem(Material.BRUSH, "Paint Brush").attach(brush)
27 | brush.onInteractEntity { player, _, _ ->
28 | val pixel = hovered ?: return@onInteractEntity
29 |
30 | bitmap[pixel] = ColorPicker.selected
31 | playSound(player.location, Sound.ITEM_BRUSH_BRUSHING_GENERIC, 1.0f, 2.0f - Random.nextFloat() * .2f)
32 | }
33 |
34 | val fill = CustomItemComponent("paint_app_fill")
35 | customItemRegistry += createNamedItem(Material.MILK_BUCKET, "Fill").attach(fill)
36 | fill.onInteractEntity { player, _, _ ->
37 | val pixel = hovered ?: return@onInteractEntity
38 |
39 | bitmap.floodFill(pixel, ColorPicker.selected)
40 | playSound(player.location, Sound.ITEM_BUCKET_EMPTY, 1.0f, 1.0f)
41 | }
42 |
43 | val eyeDropper = CustomItemComponent("paint_app_eye_dropper")
44 | customItemRegistry += createNamedItem(Material.SPIDER_EYE, "Eye Dropper").attach(eyeDropper)
45 | eyeDropper.onInteractEntity { player, _, _ ->
46 | val pixel = hovered ?: return@onInteractEntity
47 |
48 | val color = bitmap[pixel]
49 | ColorPicker.selected = color
50 |
51 | playSound(player.location, Sound.ENTITY_ARMADILLO_BRUSH, 1.0f, 1.0f)
52 | }
53 |
54 | onTick {
55 | hovered = null
56 |
57 | val entities = EntityTag("paint_app.canvas").getEntities().take(1)
58 | for (entity in entities) {
59 | // resize bitmap if necessary
60 | val width = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:bitmap_width")!!) ?: bitmap.width
61 | val height = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:bitmap_height")!!) ?: bitmap.height
62 |
63 | if (width != bitmap.width || height != bitmap.height) {
64 | bitmap = Grid(width.toInt(), height.toInt()) { Color.WHITE }
65 | }
66 |
67 | // render
68 | SharedEntityRenderer.render(entity to ::setupPaintProgram, renderCanvas(
69 | world = entity.world,
70 | position = entity.location.toVector(),
71 | quaternion = entity.location.getQuaternion(),
72 |
73 | bitmap = bitmap,
74 | height = entity.persistentDataContainer.getFloat(NamespacedKey.fromString("paint_app:display_height")!!) ?: 2f,
75 |
76 | players = entity.world.players,
77 | onHover = { _, pixel ->
78 | hovered = pixel
79 | }
80 | ))
81 | }
82 | }
83 | }
84 |
85 |
86 | @Suppress("SameParameterValue")
87 | private fun renderCanvas(
88 | world: World,
89 | position: Vector,
90 | quaternion: Quaternionf,
91 |
92 | bitmap: Grid,
93 | height: Float,
94 |
95 | players: List,
96 | onHover: (Player, Pair) -> Unit
97 | ): RenderEntityGroup {
98 | val group = RenderEntityGroup()
99 | val pointDetector = PlanePointDetector(players, position)
100 |
101 | val width = height * bitmap.width / bitmap.height
102 | val heightPerStep = height / bitmap.height
103 | val widthPerStep = width / bitmap.width
104 |
105 | val cursorSize = .05f
106 |
107 | var hovered: Pair? = null
108 |
109 | fun Matrix4f.translatePixel(pixel: Pair): Matrix4f {
110 | val (x, y) = pixel
111 | return translate(x * widthPerStep - width / 2, y * heightPerStep, 0f)
112 | }
113 |
114 | for (pixel in bitmap.indices()) {
115 | val transform = Matrix4f()
116 | .rotate(quaternion)
117 | .translatePixel(pixel)
118 | .scale(widthPerStep, heightPerStep, 1f)
119 |
120 | pointDetector.lookingAt(transform).forEach { player ->
121 | hovered = pixel
122 | onHover(player, pixel)
123 | group.add(player, RenderEntity(
124 | clazz = Interaction::class.java,
125 | location = player.location,
126 | init = {
127 | it.interactionWidth = player.width.toFloat()
128 | it.interactionHeight = player.height.toFloat()
129 | }
130 | ))
131 | }
132 |
133 | group.add(pixel, textRenderEntity(
134 | world = world,
135 | position = position,
136 | init = {
137 | it.text = " "
138 | it.brightness = Display.Brightness(15, 15)
139 | it.interpolationDuration = 1
140 | },
141 | update = {
142 | it.backgroundColor = bitmap[pixel]
143 | it.interpolateTransform(
144 | Matrix4f(transform).mul(textBackgroundTransform))
145 | }
146 | ))
147 | }
148 |
149 |
150 | val cursor = hovered
151 |
152 | if (cursor != null) {
153 | val transform = Matrix4f()
154 | .rotate(quaternion)
155 | .translate(widthPerStep / 2, heightPerStep / 2, 0f)
156 | .translatePixel(cursor)
157 | .scale(cursorSize, cursorSize, 1f)
158 | .rotateZ(Math.PI.toFloat() / 4)
159 | .translate(-.5f, -.5f, .001f)
160 |
161 | group.add("cursor", textRenderEntity(
162 | world = world,
163 | position = position,
164 | init = {
165 | it.text = " "
166 | it.brightness = Display.Brightness(15, 15)
167 | it.interpolationDuration = 1
168 | },
169 | update = {
170 | it.backgroundColor = bitmap[cursor].invert()
171 | it.interpolateTransform(
172 | Matrix4f(transform).mul(textBackgroundTransform))
173 | }
174 | ))
175 | }
176 |
177 | return group
178 | }
179 |
180 |
181 | private fun Color.invert(): Color {
182 | return Color.fromRGB(255 - red, 255 - green, 255 - blue)
183 | }
184 |
185 | private fun Grid.floodFill(pixel: Pair, color: Color) {
186 | val targetColor = this[pixel]
187 |
188 | val queue = mutableListOf(pixel)
189 | val visited = mutableSetOf(pixel)
190 |
191 | while (queue.isNotEmpty()) {
192 | val current = queue.removeAt(0)
193 | this[current] = color
194 |
195 | for (neighbor in current.neighbors()) {
196 | if (neighbor in this && neighbor !in visited && this[neighbor] == targetColor) {
197 | queue.add(neighbor)
198 | visited.add(neighbor)
199 | }
200 | }
201 | }
202 | }
203 |
204 | private fun Pair.neighbors(): List> {
205 | val (x, y) = this
206 | return listOf(
207 | x - 1 to y,
208 | x + 1 to y,
209 | x to y - 1,
210 | x to y + 1
211 | )
212 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/particle_system/FireFlyParticle.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.particle_system
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
6 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
7 | import org.bukkit.Color
8 | import org.bukkit.World
9 | import org.bukkit.entity.Display
10 | import org.bukkit.util.Vector
11 | import org.joml.Matrix4f
12 | import org.joml.Quaterniond
13 | import org.joml.Quaternionf
14 | import kotlin.math.sin
15 | import kotlin.random.Random
16 |
17 | class FireFlyParticle(
18 | val world: World,
19 | val position: Vector
20 | ): Particle {
21 | val spawnPosition = position.clone()
22 |
23 | var age = 0
24 | val maxAge = 20
25 |
26 | val orientation = Quaternionf()
27 |
28 | var opacity = 0f
29 | val fadeInTicks = 10
30 | var fadeOutTicks = 10
31 |
32 | val velocity = Vector()
33 |
34 | val color = Color.YELLOW.lerpRGB(Color.WHITE, .5)
35 | val blinkColor = Color.BLACK
36 |
37 | var blinkTick = 0
38 |
39 | fun keepAlive() {
40 | age = 0
41 | }
42 |
43 | override fun update() {
44 | age++
45 | blinkTick--
46 |
47 | if (age > maxAge) {
48 | opacity = opacity.moveTowards(0f, 255f / fadeOutTicks)
49 | if (opacity <= .0) {
50 | particles.remove(this)
51 | }
52 | return
53 | }
54 |
55 | opacity = opacity.moveTowards(255f, 255f / fadeInTicks)
56 |
57 |
58 | val blinkDuration = 20
59 | if (blinkTick <= 0) blinkTick = blinkDuration + Random.nextInt(20,100)
60 |
61 | val blink = if (blinkTick in 0..>,
21 | ): Particle {
22 | val originalPosition = position.clone()
23 |
24 | var rotSpeed = Random.nextFloat() * .4f - .2f
25 |
26 | var age = 0
27 | val maxAge = 15//Random.nextInt(10, 20)
28 | val velocity = Vector(
29 | Random.nextDouble(-.1, .1),
30 | Random.nextDouble(-.1, .1),
31 | Random.nextDouble(-.1, .1)
32 | ).normalize().multiply(.1)
33 |
34 | override fun update() {
35 | age++
36 |
37 | if (age > maxAge) {
38 | particles.remove(this)
39 | return
40 | }
41 |
42 | position.add(velocity)
43 |
44 | // rise
45 | velocity.y += .03
46 |
47 | // pull towards the center
48 | val center = originalPosition.clone().apply { y = position.y }
49 | val direction = center.clone().subtract(position).normalize()
50 | velocity.add(direction.multiply(.02))
51 |
52 | // air drag
53 | velocity.multiply(.9)
54 |
55 | SharedEntityRenderer.render(this, textRenderEntity(
56 | world = world,
57 | position = position,
58 | init = {
59 | it.text = " "
60 | it.brightness = Brightness(15, 15)
61 | it.billboard = Display.Billboard.CENTER
62 | it.interpolationDuration = 1
63 | it.teleportDuration = 1
64 | },
65 | update = {
66 | val transform = Matrix4f()
67 | // .translate(position.toVector3f().sub(originalPosition.toVector3f()))
68 | .rotateZ(age * rotSpeed)
69 | .scale(.1f)
70 | .translate(-.5f, -.5f, 0f)
71 | .mul(textBackgroundTransform)
72 |
73 | it.interpolateTransform(transform)
74 | it.backgroundColor = palette.interpolateOkLab(age.toDouble() / maxAge)
75 | }
76 | ))
77 | }
78 | }
79 |
80 | private val yellow = Color.fromRGB(252, 211, 3)
81 | private val orange = Color.fromRGB(255, 136, 0)
82 | private val red = Color.fromRGB(222, 64, 11)
83 |
84 | val blueToOrangeFlamePalette = listOf(
85 | 0.00 to Color.WHITE,
86 | 0.05 to Color.WHITE,
87 | 0.10 to Color.fromRGB(64, 188, 255), // light blue
88 | 0.30 to Color.fromRGB(0, 17, 201), // blue
89 | 0.50 to yellow,
90 | 0.70 to red,
91 | 0.80 to Color.BLACK,
92 | 1.0 to Color.BLACK.setAlpha(0),
93 | )
94 |
95 |
96 | val orangeFlamePalette = listOf(
97 | 0.00 to Color.WHITE,
98 | 0.05 to Color.WHITE,
99 | 0.20 to yellow,
100 | 0.40 to orange,
101 | 0.70 to red,
102 | 0.80 to Color.BLACK,
103 | 1.0 to Color.BLACK.setAlpha(0),
104 | )
105 |
106 |
107 | val blackFlamePalette = listOf(
108 | 0.00 to Color.WHITE,
109 | 0.10 to Color.WHITE,
110 | 0.60 to Color.BLACK,
111 | 0.70 to Color.BLACK,
112 | 1.0 to Color.BLACK.setAlpha(0),
113 | )
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/particle_system/WaterParticle.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.particle_system
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
6 | import com.heledron.text_display_experiments.utilities.rendering.interpolateTransform
7 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
8 | import org.bukkit.Color
9 | import org.bukkit.World
10 | import org.bukkit.entity.Display
11 | import org.bukkit.util.Vector
12 | import org.joml.Matrix4f
13 | import org.joml.Quaterniond
14 | import kotlin.math.pow
15 | import kotlin.random.Random
16 |
17 | class WaterParticle(
18 | val world: World,
19 | val source: Vector,
20 | val position: Vector,
21 | val minSize: Double,
22 | val maxSize: Double,
23 | val minSpeed: Double,
24 | val maxSpeed: Double,
25 | maxUpAngle: Double = 85.0.toRadians(),
26 | upAngleBias: Int
27 | ) : Particle {
28 | val color = Color.WHITE.lerpOkLab(Color.fromRGB(0x699cc9), Random.nextDouble().pow(4))
29 |
30 | var gravityAcceleration = .08
31 | var airDragCoefficient = .02 * 3
32 |
33 | var age = 0
34 | val maxAge = Random.nextInt(15,25)
35 |
36 | val velocity = FORWARD_VECTOR
37 | .rotate(
38 | Quaterniond()
39 | .rotateY(position.clone().subtract(source).yaw().toDouble())
40 | .rotateX(-maxUpAngle * (1 - Random.nextDouble().pow(upAngleBias)))
41 | )
42 | .multiply(Random.nextDouble(minSpeed, maxSpeed + .000001))
43 |
44 | var rotSpeed = Random.nextDouble(-.2, .2).toFloat()
45 | var size = Random.nextDouble(minSize, maxSize + .000001).toFloat()
46 |
47 |
48 | override fun update() {
49 | age += 1
50 | if (age > maxAge) {
51 | particles.remove(this)
52 | return
53 | }
54 |
55 | // accelerate due to gravity
56 | velocity.y -= gravityAcceleration
57 |
58 | // slow down when in water
59 | if (position.y < source.y && velocity.y < 0) {
60 | velocity.multiply(.2)
61 | age += 3
62 | }
63 |
64 | // air drag
65 | velocity.multiply(1 - airDragCoefficient)
66 |
67 | // move
68 | val oldPosition = position.clone()
69 | position.add(velocity)
70 |
71 | val justEnteredWater = oldPosition.y > source.y && position.y <= source.y
72 | if (justEnteredWater && Random.nextBoolean()) {
73 | // bounce
74 | position.y = source.y
75 | velocity.y = -velocity.y * .5
76 | velocity.x *= .3
77 | velocity.z *= .3
78 | }
79 |
80 | // render
81 | SharedEntityRenderer.render(this, textRenderEntity(
82 | world = world,
83 | position = position,
84 | init = {
85 | it.text = " "
86 | it.billboard = Display.Billboard.CENTER
87 | it.interpolationDuration = 1
88 | it.teleportDuration = 1
89 | // it.brightness = Brightness(15, 15)
90 | },
91 | update = {
92 | val transform = Matrix4f()
93 | .rotateZ(age * rotSpeed)
94 | .scale(size)
95 | .translate(-.5f, -.5f, 0f)
96 | .mul(textBackgroundTransform)
97 |
98 | it.interpolateTransform(transform)
99 |
100 |
101 | it.backgroundColor = color.setAlpha((color.alpha * (1 - age.toDouble() / maxAge)).toInt().coerceIn(0, 255))
102 | }
103 | ))
104 | }
105 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/particle_system/setupParticleSystem.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.particle_system
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import org.bukkit.NamespacedKey
5 | import kotlin.random.Random
6 |
7 | fun setupParticleSystem() {
8 | onSpawnEntity {
9 | val paletteName = it.persistentDataContainer.getString(NamespacedKey.fromString("flame_particles:palette")!!) ?: return@onSpawnEntity
10 |
11 | val palette = when (paletteName) {
12 | "blue_to_orange" -> blueToOrangeFlamePalette
13 | "orange" -> orangeFlamePalette
14 | "black" -> blackFlamePalette
15 | else -> blackFlamePalette
16 | }
17 |
18 | for (i in 0 until 10) particles += FlameParticle(
19 | world = it.world,
20 | position = it.location.toVector(),
21 | palette = palette
22 | )
23 | }
24 |
25 | onSpawnEntity {
26 | val amount = it.persistentDataContainer.getInt(NamespacedKey.fromString("water_splash_particles:amount")!!) ?: 0
27 | val minSize = it.persistentDataContainer.getDouble(NamespacedKey.fromString("water_splash_particles:min_size")!!) ?: .1
28 | val maxSize = it.persistentDataContainer.getDouble(NamespacedKey.fromString("water_splash_particles:max_size")!!) ?: (minSize * 1.75)
29 |
30 | val minSpeed = it.persistentDataContainer.getDouble(NamespacedKey.fromString("water_splash_particles:min_speed")!!) ?: .3
31 | val maxSpeed = it.persistentDataContainer.getDouble(NamespacedKey.fromString("water_splash_particles:max_speed")!!) ?: (minSpeed * 2.3)
32 |
33 | val upAngleBias = it.persistentDataContainer.getInt(NamespacedKey.fromString("water_splash_particles:up_angle_bias")!!) ?: 4
34 |
35 | for (i in 0 until amount) particles += WaterParticle(
36 | world = it.world,
37 | source = it.location.toVector(),
38 | position = it.location.toVector().apply {
39 | val variance = 12/16.0
40 | x += Random.nextDouble(-variance, variance)
41 | z += Random.nextDouble(-variance, variance)
42 | },
43 | minSize = minSize,
44 | maxSize = maxSize,
45 | minSpeed = minSpeed,
46 | maxSpeed = maxSpeed,
47 | upAngleBias = upAngleBias
48 | )
49 | }
50 |
51 | onSpawnEntity {
52 | val maxFireflies = it.persistentDataContainer.getInt(NamespacedKey.fromString("firefly_particles:amount")!!) ?: return@onSpawnEntity
53 |
54 | val range = 7.0
55 | val spawnRange = .0
56 |
57 | val position = it.location.toVector()
58 |
59 | val fireflies = particles.filterIsInstance().filter { firefly ->
60 | firefly.position.distance(position) < range
61 | }
62 |
63 | for (firefly in fireflies) firefly.keepAlive()
64 |
65 | for (i in fireflies.size until maxFireflies) particles += FireFlyParticle(
66 | world = it.world,
67 | position = position.clone().apply {
68 | x += Random.nextDouble(-spawnRange, spawnRange + .0001)
69 | z += Random.nextDouble(-spawnRange, spawnRange + .0001)
70 | }
71 | )
72 |
73 | }
74 |
75 | onTick {
76 | particles.toList().forEach { it.update() }
77 | }
78 | }
79 |
80 | internal val particles = mutableListOf()
81 |
82 | interface Particle {
83 | fun update()
84 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/point_detector_visualizer/PointDetectorVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.point_detector_visualizer
2 |
3 | import com.heledron.text_display_experiments.textBackgroundTransform
4 | import com.heledron.text_display_experiments.utilities.*
5 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
6 | import com.heledron.text_display_experiments.utilities.rendering.blockRenderEntity
7 | import com.heledron.text_display_experiments.utilities.rendering.interpolateTransform
8 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
9 | import org.bukkit.Color
10 | import org.bukkit.Material
11 | import org.bukkit.Sound
12 | import org.bukkit.World
13 | import org.bukkit.entity.Display
14 | import org.bukkit.util.Vector
15 | import org.joml.Matrix4f
16 | import org.joml.Quaternionf
17 |
18 | fun setUpPointDetectorVisualizer() {
19 | val item = CustomItemComponent("point_detector_visualizer")
20 | customItemRegistry += createNamedItem(Material.BLAZE_ROD, "Next").apply { item.attach(this) }
21 |
22 |
23 | var plane: Triple? = null
24 | var line: Pair? = null
25 | var inverted = false
26 | var invertedTime = 0
27 |
28 |
29 | item.onGestureUse { player, _ ->
30 |
31 |
32 | // reset
33 | if (inverted) {
34 | playSound(player.location, Sound.BLOCK_DISPENSER_FAIL, 1.0f, 1.5f)
35 | plane = null
36 | line = null
37 | inverted = false
38 | return@onGestureUse
39 | }
40 |
41 | playSound(player.location, Sound.BLOCK_DISPENSER_FAIL, 1.0f, 2.0f)
42 |
43 | // spawn plane
44 | if (plane == null) {
45 | val offset = Vector(1.1,1.1,.0)
46 | plane = Triple(
47 | player.world,
48 | player.eyeLocation.toVector().add(player.eyeLocation.direction.multiply(3)).subtract(offset),
49 | Matrix4f()
50 | .translate(offset.toVector3f())
51 | .scale(.5f,.4f,.3f)
52 | .rotateYXZ(.5f, .4f, .0f)
53 | .scale(2f,1f,1f)
54 | .rotateZ(45f.toRadians())
55 | )
56 | return@onGestureUse
57 | }
58 |
59 | // spawn line
60 | if (line == null) {
61 | line = Pair(
62 | player.eyeLocation.toVector().add(player.eyeLocation.direction.multiply(.1)),
63 | player.eyeLocation.toVector().add(player.eyeLocation.direction.multiply(10)),
64 | )
65 | return@onGestureUse
66 | }
67 |
68 | if (!inverted) {
69 | inverted = true
70 | invertedTime = 0
71 | return@onGestureUse
72 | }
73 | }
74 |
75 | onTick {
76 | val plane = plane ?: return@onTick
77 | val world = plane.first
78 |
79 | if (inverted) invertedTime += 1
80 | else invertedTime = 0
81 |
82 | val lerpAmount = (invertedTime / 15.0).coerceIn(0.0, 1.0)
83 | val eased = lerpAmount * lerpAmount * (3 - 2 * lerpAmount)
84 | val invertMatrix = Matrix4f().lerp(Matrix4f(plane.third).invert(), eased.toFloat())
85 |
86 | val planeColor = Color.fromRGB(0x9c0610)
87 | val invertedColor = Color.fromRGB(0x069c9c).setAlpha((255 * .75).toInt())
88 |
89 | SharedEntityRenderer.render("point_detector_visualizer_plane", textRenderEntity(
90 | world = world,
91 | position = plane.second,
92 | init = {
93 | it.text = " "
94 | it.brightness = Display.Brightness(15, 15)
95 | it.backgroundColor = planeColor
96 | },
97 | update = {
98 | it.setTransformationMatrix(Matrix4f(plane.third).mul(textBackgroundTransform))
99 | }
100 | ))
101 |
102 |
103 | SharedEntityRenderer.render("point_detector_visualizer_plane_transformed", textRenderEntity(
104 | world = world,
105 | position = plane.second,
106 | init = {
107 | it.text = " "
108 | it.brightness = Display.Brightness(15, 15)
109 | it.teleportDuration = 1
110 | it.interpolationDuration = 1
111 | },
112 | update = {
113 | it.backgroundColor = invertedColor.setAlpha((invertedColor.alpha * eased).toInt())
114 | it.interpolateTransform(Matrix4f(invertMatrix).mul(plane.third).mul(textBackgroundTransform))
115 | }
116 | ))
117 |
118 |
119 | line?.let { line ->
120 | val point1 = line.first.clone().subtract(plane.second)
121 | val point2 = line.second.clone().subtract(plane.second)
122 |
123 | SharedEntityRenderer.render("point_detector_visualizer_line", blockRenderEntity(
124 | world = world,
125 | position = plane.second,
126 | init = {
127 | it.block = Material.GOLD_BLOCK.createBlockData()
128 | it.brightness = Display.Brightness(15, 15)
129 | it.teleportDuration = 1
130 | it.interpolationDuration = 1
131 | },
132 | update = {
133 | val thickness = .02f
134 | val diff = point2.clone().subtract(point1)
135 | val length = diff.length()
136 | val rotation = Quaternionf().rotationTo(FORWARD_VECTOR.toVector3f(), diff.toVector3f())
137 | it.interpolateTransform(
138 | Matrix4f()
139 | .mul(invertMatrix)
140 | .translate(point1.toVector3f())
141 | .rotate(rotation)
142 | .scale(thickness, thickness, length.toFloat())
143 | .translate(-.5f, -.5f, 0f)
144 |
145 | )
146 | }
147 | ))
148 | }
149 | }
150 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/setupCloak.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import com.heledron.text_display_experiments.utilities.rendering.*
5 | import org.bukkit.*
6 | import org.bukkit.util.RayTraceResult
7 | import org.bukkit.util.Vector
8 | import org.joml.Matrix4f
9 | import org.joml.Quaternionf
10 | import org.joml.Vector4f
11 | import kotlin.random.Random
12 |
13 | fun setupCloak() {
14 | val cloak = CustomItemComponent("cloak")
15 | customItemRegistry += createNamedItem(Material.GREEN_DYE, "Handheld Cloak").attach(cloak)
16 |
17 | cloak.onHeldTick { player, _ ->
18 | val alpha = player.scoreboardTags.firstOrNull { it.startsWith("cloak_alpha.") }?.substringAfter(".")?.toIntOrNull() ?: 255
19 | val noise = player.scoreboardTags.firstOrNull { it.startsWith("cloak_noise.") }?.substringAfter(".")?.toDoubleOrNull() ?: 30.0
20 |
21 |
22 | SharedEntityRenderer.render("cloak" to player, cloak(
23 | world = player.world,
24 | position = player.eyeLocation.toVector(),
25 | rotation = player.eyeLocation.getQuaternion(),
26 | eyePosition = player.eyeLocation.toVector().add(player.eyeLocation.direction.multiply(4.0)),
27 | alpha = alpha,
28 | noiseAmount = noise,
29 | ))
30 |
31 | }
32 | }
33 |
34 | fun cloak(
35 | world: World,
36 | position: Vector,
37 | rotation: Quaternionf,
38 | eyePosition: Vector,
39 | alpha: Int,
40 | noiseAmount: Double,
41 | ): RenderEntityGroup {
42 |
43 | val xSize = 1f
44 | val ySize = 1.5f
45 |
46 | val xItems = 16
47 | val yItems = (xItems * ySize / xSize).toInt()
48 |
49 | val forwardDistance = 1.5f
50 | val castDistance = 100.0
51 |
52 | val group = RenderEntityGroup()
53 | for (x in 0 until xItems) {
54 | for (y in 0 until yItems) {
55 | val xItemSize = xSize / xItems
56 | val yItemSize = ySize / yItems
57 |
58 | val transform = Matrix4f()
59 | .rotate(rotation)
60 | .translate(-xSize / 2f, -.5f - ySize / 2f, 0f)
61 | .translate(x * xItemSize, y * yItemSize, forwardDistance)
62 | .scale(xItemSize, yItemSize, 1f)
63 |
64 | group.add(x to y, textRenderEntity(
65 | world = world,
66 | position = position,
67 | init = {
68 | it.text = " "
69 | it.interpolationDuration = 1
70 | it.teleportDuration = 1
71 | },
72 | update = {
73 | it.interpolateTransform(Matrix4f(transform).mul(textBackgroundTransform))
74 |
75 | val relative = transform.transform(Vector4f(.5f,.5f,.0f,1f)).toVector3f()
76 | val center = position.clone().add(Vector().copy(relative))
77 | val direction = center.subtract(eyePosition).normalize()
78 |
79 | val hitBlock = world.raycastGround(eyePosition, direction, castDistance)
80 | val data = hitBlock?.hitBlock?.blockData
81 |
82 | val newColor = if (data !== null) {
83 | val color = getColorFromBlock(data) ?: it.backgroundColor ?: return@textRenderEntity
84 |
85 | val positionSeed = hitBlock.hitPosition
86 | val seed = (positionSeed.x * 1000 + positionSeed.y * 100 + positionSeed.z * 10).toInt() + x * 10000 + y * 10_0000
87 |
88 | color.noise(noiseAmount, seed).setAlpha(alpha)
89 | // Color.RED.setAlpha(180)
90 | } else {
91 | val skyBottomColor = Color.fromRGB(0xd6edfa)
92 | val skyTopColor = Color.fromRGB(0x5e92d4)
93 | val skyBottom = (-20.0).toRadians()
94 | val skyTop = 40.0.toRadians()
95 | val pitch = direction.pitch()
96 |
97 | val fraction = ((pitch - skyBottom) / (skyTop - skyBottom)).coerceIn(.0, 1.0)
98 |
99 | skyBottomColor.lerpOkLab(skyTopColor, fraction).setAlpha(alpha) //.setAlpha(0)
100 | }
101 |
102 | it.backgroundColor = (it.backgroundColor ?: newColor).lerpOkLab(newColor, .3)
103 | }
104 | ))
105 |
106 | }
107 | }
108 |
109 | return group
110 | }
111 |
112 |
113 | @Suppress("SameParameterValue")
114 | private fun World.raycastGround(position: Vector, direction: Vector, maxDistance: Double): RayTraceResult? {
115 | return this.rayTraceBlocks(position.toLocation(this), direction, maxDistance, FluidCollisionMode.NEVER, true)
116 | }
117 |
118 | private fun Color.noise(amount: Double, seed: Int): Color {
119 | if (amount == 0.0) return this
120 |
121 | val random = Random(seed)
122 | val noise = random.nextDouble(-amount, amount)
123 | return Color.fromARGB(
124 | alpha,
125 | (red + noise).coerceIn(0.0, 255.0).toInt(),
126 | (green + noise).coerceIn(0.0, 255.0).toInt(),
127 | (blue + noise).coerceIn(0.0, 255.0).toInt(),
128 | )
129 | }
130 |
131 |
132 |
133 | //SharedEntityRenderer.render("test" to x to y, blockRenderEntity(
134 | //world = player.world,
135 | //position = startCast.toVector(),
136 | //init = {
137 | // it.brightness = Brightness(15, 15)
138 | // it.interpolationDuration = 1
139 | // it.block = Material.REDSTONE_BLOCK.createBlockData()
140 | //},
141 | //update = {
142 | // it.setTransformationMatrix(Matrix4f()
143 | // .rotate(Quaternionf().rotateTo(FORWARD_VECTOR.toVector3f(), direction.toVector3f()))
144 | // .scale(.01f, .01f, 15f)
145 | // .translate(.5f, .5f, 0f)
146 | // )
147 | //}
148 | //))
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/setupColoredCauldrons.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
5 | import com.heledron.text_display_experiments.utilities.rendering.blockRenderEntity
6 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
7 | import org.bukkit.Color
8 | import org.bukkit.NamespacedKey
9 | import kotlin.math.floor
10 |
11 | fun setupColoredCauldrons() {
12 | val tag = EntityTag("colored_cauldron")
13 |
14 | tag.onTick { marker ->
15 | val targetColor =
16 | marker.persistentDataContainer.getColor(NamespacedKey.fromString("colored_cauldron:background")!!) ?:
17 | Color.fromARGB(0xaa76a4cf.toInt())
18 |
19 | marker.location.block.setType(org.bukkit.Material.BARRIER, false)
20 |
21 | val floored = marker.location.clone().apply {
22 | x = floor(x)
23 | y = floor(y)
24 | z = floor(z)
25 | pitch = 0f
26 | yaw = 0f
27 | }
28 |
29 | SharedEntityRenderer.render(marker, textRenderEntity(
30 | location = floored.clone().apply {
31 | y = floor(y) + (14 / 16.0)
32 | z = floor(z) + 1
33 | pitch = -90f
34 | },
35 | init = {
36 | it.text = " "
37 | it.interpolationDuration = 1
38 | it.teleportDuration = 1
39 | it.setTransformationMatrix(textBackgroundTransform)
40 | },
41 | update = {
42 | val newColor = (it.backgroundColor ?: targetColor).lerpOkLab(targetColor, .2)
43 | it.backgroundColor = newColor
44 | }
45 | ))
46 |
47 | SharedEntityRenderer.render(marker to "cauldron", blockRenderEntity(
48 | location = floored.clone(),
49 | init = {
50 | it.block = org.bukkit.Material.CAULDRON.createBlockData()
51 | }
52 | ))
53 | }
54 |
55 | tag.onInteract { event ->
56 | val item = event.player.inventory.getItem(event.hand)
57 | val entity = event.rightClicked
58 |
59 | val addColor = when (item?.type) {
60 | org.bukkit.Material.RED_DYE -> Color.fromARGB(0xaaFF0000L.toInt())
61 | org.bukkit.Material.GREEN_DYE -> Color.fromARGB(0xcc00FF00L.toInt())
62 | org.bukkit.Material.BLUE_DYE -> Color.fromARGB(0xaa0000FFL.toInt())
63 |
64 | // clear
65 | org.bukkit.Material.LAPIS_LAZULI -> {
66 | entity.persistentDataContainer.remove(NamespacedKey.fromString("colored_cauldron:background")!!)
67 | playSound(entity.location, org.bukkit.Sound.ITEM_BOTTLE_EMPTY, 1f, 0f)
68 | return@onInteract
69 | }
70 | else -> return@onInteract
71 | }
72 |
73 | val oldColor = entity.persistentDataContainer.getColor(NamespacedKey.fromString("colored_cauldron:background")!!)
74 | val newColor = oldColor?.lerpOkLab(addColor, .5) ?: addColor
75 |
76 | entity.persistentDataContainer.setColor(NamespacedKey.fromString("colored_cauldron:background")!!, newColor)
77 | playSound(entity.location, org.bukkit.Sound.ITEM_BOTTLE_EMPTY, 1f, .8f)
78 | }
79 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/setupTextEntityUtilities.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
5 | import com.heledron.text_display_experiments.utilities.rendering.textRenderEntity
6 | import org.bukkit.Bukkit
7 | import org.bukkit.Color
8 | import org.bukkit.NamespacedKey
9 | import org.bukkit.entity.Display
10 | import org.bukkit.entity.TextDisplay
11 | import org.joml.Matrix4f
12 | import org.joml.Quaternionf
13 | import kotlin.math.sin
14 |
15 | fun setupTextEntityUtilities() {
16 | // Rainbow cycle
17 | var hue = 0
18 | onTick {
19 | val entities = EntityTag("rainbow_cycle_animation").getEntities()
20 |
21 | if (entities.isEmpty()) {
22 | hue = 0
23 | return@onTick
24 | }
25 |
26 | hue += 360 / (20 * 3)
27 | hue %= 360
28 |
29 | val color = hsv(hue.toDouble(), 1.0, 1.0)
30 | for (entity in entities) {
31 | (entity as? TextDisplay)?.backgroundColor = color
32 | }
33 | }
34 |
35 | // Pulsating animation
36 | val minOpacity = 255 * (1.0/4)
37 | val maxOpacity = 255 * (2.0/4)
38 | val period = 5
39 | var currentTime = 0
40 | onTick {
41 | val entities = EntityTag("pulsating_animation").getEntities().filterIsInstance()
42 |
43 | if (entities.isEmpty()) {
44 | currentTime = 0
45 | return@onTick
46 | }
47 |
48 | currentTime++
49 |
50 | val opacity = minOpacity + (maxOpacity - minOpacity) * (sin(currentTime.toDouble() / period) + 1) / 2
51 | for (entity in entities) {
52 | val color = Color.fromARGB(0x0000CCCC)
53 | entity.backgroundColor = color.setAlpha(opacity.toInt())
54 | }
55 | }
56 |
57 |
58 | // Screen overlay
59 | val transforms = listOf(
60 | Quaternionf(),
61 | Quaternionf().rotateY(Math.PI.toFloat() / 2 * 1),
62 | Quaternionf().rotateY(Math.PI.toFloat() / 2 * 2),
63 | Quaternionf().rotateY(Math.PI.toFloat() / 2 * 3),
64 |
65 | Quaternionf().rotateX(Math.PI.toFloat() / 2),
66 | Quaternionf().rotateX(-Math.PI.toFloat() / 2),
67 | ).map {
68 | val size = 2.5f
69 | Matrix4f()
70 | .rotate(it)
71 | .scale(size,size,1f)
72 | .translate(-.5f, -.5f, -size / 2)
73 | .mul(textBackgroundTransform)
74 | }
75 |
76 | onTick {
77 | for (player in Bukkit.getServer().onlinePlayers) {
78 | val tag = player.scoreboardTags.firstOrNull { it.startsWith("screen_overlay.") } ?: continue
79 | player.removeScoreboardTag(tag)
80 |
81 | val colorCode = tag.substringAfterLast(".")
82 |
83 | player.scoreboardTags.removeIf { it.startsWith("screen_overlay_active.") }
84 | if (colorCode != "clear") player.scoreboardTags.add("screen_overlay_active.$colorCode")
85 | }
86 |
87 | for (player in Bukkit.getServer().onlinePlayers) {
88 | val tag = player.scoreboardTags.firstOrNull { it.startsWith("screen_overlay_active.") } ?: continue
89 | val colorCode = tag.substringAfterLast(".")
90 | val color = try {
91 | Color.fromARGB(colorCode.toLong(radix = 16).toInt())
92 | } catch (e: NumberFormatException) {
93 | continue
94 | } catch (e: IllegalArgumentException) {
95 | continue
96 | }
97 |
98 | for ((i,transform) in transforms.withIndex()) SharedEntityRenderer.render("screen_overlay" to player to i, textRenderEntity(
99 | world = player.world,
100 | position = player.eyeLocation.toVector(),
101 | init = {
102 | it.text = " "
103 | it.brightness = Display.Brightness(15, 15)
104 | it.teleportDuration = 1
105 | it.setTransformationMatrix(transform)
106 | },
107 | update = {
108 | it.backgroundColor = color
109 |
110 | // TODO: Remove when this bug is resolved
111 | // https://bugs.mojang.com/browse/MC-259812
112 | it.isSeeThrough = true
113 | }
114 | ))
115 | }
116 | }
117 |
118 | // Background color utilities
119 | onTick {
120 | for (entity in Bukkit.getServer().worlds.flatMap { it.entities }.filterIsInstance()) {
121 | val newColor = entity.persistentDataContainer.getColor(NamespacedKey.fromString("text_utilities:background")!!) ?: continue
122 | val lerpSpeed = entity.persistentDataContainer.getDouble(NamespacedKey.fromString("text_utilities:background_lerp_speed")!!) ?: 1.0
123 |
124 | val oldColor = entity.backgroundColor ?: newColor
125 | entity.backgroundColor = oldColor.lerpRGB(newColor, lerpSpeed)
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/Grid.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | class Grid(
4 | val width: Int,
5 | val height: Int,
6 | initial: (Pair) -> T
7 | ) {
8 | private val data = MutableList(width * height) { initial(it % width to it / width) }
9 |
10 | operator fun get(index: Int): T {
11 | return data[index]
12 | }
13 |
14 | operator fun set(index: Int, color: T) {
15 | data[index] = color
16 | }
17 |
18 | operator fun get(index: Pair): T {
19 | val (x, y) = index
20 | return this[y * width + x]
21 | }
22 |
23 | operator fun set(index: Pair, color: T) {
24 | val (x, y) = index
25 | this[y * width + x] = color
26 | }
27 |
28 | operator fun contains(index: Pair): Boolean {
29 | val (x, y) = index
30 | return x in 0 until width && y in 0 until height
31 | }
32 |
33 | fun getFraction(x: Double, y: Double): T {
34 | val xPixel = (x * width).toInt().coerceIn(0, width - 1)
35 | val yPixel = (y * height).toInt().coerceIn(0, height - 1)
36 | return this[xPixel to yPixel]
37 | }
38 |
39 | fun indices(): Sequence> = sequence {
40 | for (x in 0 until width) {
41 | for (y in 0 until height) {
42 | yield(x to y)
43 | }
44 | }
45 | }
46 |
47 | fun map(transform: (T) -> N): Grid {
48 | return Grid(width, height) { index -> transform(this[index]) }
49 | }
50 |
51 | fun setAll(provider: (Pair) -> T) {
52 | for (x in 0 until width) {
53 | for (y in 0 until height) {
54 | this[x to y] = provider(x to y)
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/Rect.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.joml.Vector2d
4 |
5 | class Rect private constructor(var minX: Double, var minY: Double, var maxX: Double, var maxY: Double) {
6 | val width; get() = maxX - minX
7 | val height; get() = maxY - minY
8 |
9 | val dimensions; get() = Vector2d(width, height)
10 |
11 | companion object {
12 | fun fromMinMax(min: Vector2d, max: Vector2d): Rect {
13 | return Rect(min.x, min.y, max.x, max.y)
14 | }
15 |
16 | fun fromCenter(center: Vector2d, dimensions: Vector2d): Rect {
17 | return Rect(
18 | center.x - dimensions.x / 2,
19 | center.y - dimensions.y / 2,
20 | center.x + dimensions.x / 2,
21 | center.y + dimensions.y / 2
22 | )
23 | }
24 | }
25 |
26 | fun clone(): Rect {
27 | return Rect(minX, minY, maxX, maxY)
28 | }
29 |
30 | fun expand(padding: Double): Rect {
31 | minX -= padding
32 | minY -= padding
33 | maxX += padding
34 | maxY += padding
35 | return this
36 | }
37 |
38 | fun setYCenter(center: Double, height: Double): Rect {
39 | minY = center - height / 2
40 | maxY = center + height / 2
41 | return this
42 | }
43 |
44 | fun lerp(other: Rect, t: Double): Rect {
45 | minX = minX.lerp(other.minX, t)
46 | minY = minY.lerp(other.minY, t)
47 | maxX = maxX.lerp(other.maxX, t)
48 | maxY = maxY.lerp(other.maxY, t)
49 | return this
50 | }
51 |
52 | fun set(other: Rect): Rect {
53 | minX = other.minX
54 | minY = other.minY
55 | maxX = other.maxX
56 | maxY = other.maxY
57 | return this
58 | }
59 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/blockColorLookUp.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import com.google.gson.Gson
4 | import org.bukkit.Color
5 | import org.bukkit.Material
6 | import org.bukkit.block.data.BlockData
7 | import kotlin.math.pow
8 | import kotlin.math.sqrt
9 |
10 | private fun String.parseJSONColors(): Map {
11 | val colorMap = mutableMapOf()
12 |
13 | @Suppress("UNCHECKED_CAST")
14 | val json = Gson().fromJson(this, Map::class.java) as Map>
15 |
16 | for ((key, value) in json) {
17 | val material = Material.matchMaterial("minecraft:$key") ?: continue
18 | colorMap[material] = Color.fromRGB(value[0], value[1], value[2])
19 | }
20 |
21 | return colorMap
22 | }
23 |
24 | private val blocks = currentPlugin.getResource("block_colors.json")
25 | ?.bufferedReader()
26 | ?.use { it.readText() }
27 | ?.parseJSONColors()
28 | ?: throw IllegalStateException("Failed to load block_colors.json")
29 |
30 | private val blocksWithBrightness = mutableMapOf>().apply {
31 | for (brightness in 15 downTo 0) {
32 | for ((material, color) in blocks) {
33 | if (!material.isOccluding) continue
34 |
35 | val newColor = color.withBrightness(brightness)
36 |
37 | if (newColor in this) continue
38 |
39 | this[newColor] = material to brightness
40 | }
41 | }
42 |
43 |
44 | for ((color, material) in this) {
45 | val id = material.first.key.toString()
46 |
47 | // replace log with wood
48 | if (id.endsWith("_log")) {
49 | val woodMaterial = Material.matchMaterial(id.replaceEnd("_log", "_wood")) ?: continue
50 | this[color] = woodMaterial to material.second
51 | }
52 | }
53 | }
54 |
55 | private val blockToColor = blocks.toMutableMap().apply {
56 | // this[Material.GRASS_BLOCK] = Color.fromRGB(0x93ac4d)
57 | // this[Material.OAK_LEAVES] = Color.fromRGB(0x6c8c25)
58 | // this[Material.BIRCH_LEAVES] = Color.fromRGB(0x6f813d)
59 | // this[Material.SPRUCE_LEAVES] = Color.fromRGB(0x395633)
60 | // this[Material.AZALEA_LEAVES] = Color.fromRGB(0x6d7c2b)
61 | //
62 | // this[Material.MOSS_BLOCK] = this[Material.GRASS_BLOCK]!!
63 | // this[Material.MOSS_CARPET] = this[Material.MOSS_BLOCK]!!
64 |
65 | this[Material.GRASS_BLOCK] = this[Material.MOSS_BLOCK]!!
66 | this[Material.OAK_LEAVES] = this[Material.MOSS_BLOCK]!!.withBrightness(10)
67 | this[Material.BIRCH_LEAVES] = this[Material.MOSS_BLOCK]!!.withBrightness(8)
68 | this[Material.SPRUCE_LEAVES] = this[Material.MOSS_BLOCK]!!.withBrightness(6)
69 |
70 | this[Material.WARPED_TRAPDOOR] = this[Material.WARPED_PLANKS]!!
71 |
72 | this[Material.BARREL] = this[Material.SPRUCE_PLANKS]!!
73 |
74 | // replace partial blocks with their full block counterparts
75 | for (material in Material.entries) {
76 | if (!material.isBlock) continue
77 | val id = material.key.toString()
78 |
79 | if (this.containsKey(material)) continue
80 |
81 | val fullBlockName = id
82 | .replaceEnd("_slab", "")
83 | .replaceEnd("_stairs", "")
84 | .replaceEnd("_wall", "")
85 | .replaceEnd("_trapdoor", "")
86 | .replace("waxed_", "")
87 |
88 | if (fullBlockName == id) continue
89 |
90 | val fullBlockMaterial =
91 | Material.matchMaterial(fullBlockName + "_planks") ?:
92 | Material.matchMaterial(fullBlockName) ?:
93 | Material.matchMaterial(fullBlockName + "s") ?:
94 | Material.matchMaterial(fullBlockName + "_wood")
95 |
96 | this[material] = this[fullBlockMaterial] ?: continue
97 | }
98 |
99 | this[Material.CAMPFIRE] = this[Material.OAK_LOG]!!
100 | }
101 |
102 | fun getColorFromBlock(block: BlockData): Color? {
103 | return blockToColor[block.material]
104 | }
105 |
106 | fun getColorFromBlock(block: BlockData, brightness: Int): Color? {
107 | return getColorFromBlock(block)?.withBrightness(brightness)
108 | }
109 |
110 | class MatchInfo(
111 | val block: BlockData,
112 | val blockColor: Color,
113 | val distance: Double,
114 | val brightness: Int,
115 | )
116 |
117 | fun getBestMatchFromColor(color: Color, allowCustomBrightness: Boolean): MatchInfo {
118 | val map = if (allowCustomBrightness) blocksWithBrightness else blocksWithBrightness.filterValues { it.second == 15 }
119 |
120 | val bestMatch = map.minBy { it.key.distanceTo(color) }
121 | return MatchInfo(bestMatch.value.first.createBlockData(), bestMatch.key, color.distanceTo(bestMatch.key), bestMatch.value.second)
122 | }
123 |
124 | private fun String.replaceEnd(suffix: String, with: String): String {
125 | return if (endsWith(suffix)) {
126 | substring(0, length - suffix.length) + with
127 | } else {
128 | this
129 | }
130 | }
131 |
132 | private fun Color.distanceTo(other: Color): Double {
133 | return sqrt(
134 | (red - other.red).toDouble().pow(2) +
135 | (green - other.green).toDouble().pow(2) +
136 | (blue - other.blue).toDouble().pow(2)
137 | )
138 | }
139 |
140 | private fun Color.withBrightness(brightness: Int): Color {
141 | return Color.fromRGB(
142 | (red * brightness.toDouble() / 15).toInt(),
143 | (green * brightness.toDouble() / 15).toInt(),
144 | (blue * brightness.toDouble() / 15).toInt(),
145 | )
146 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/colors.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Color
4 | import kotlin.math.abs
5 |
6 |
7 | fun Color.blendAlpha(other: Color): Color {
8 | val alpha = this.alpha / 255.0
9 | val otherAlpha = other.alpha / 255.0
10 | val blendedAlpha = alpha + otherAlpha * (1 - alpha)
11 | val r = (this.red * alpha + other.red * otherAlpha * (1 - alpha)) / blendedAlpha
12 | val g = (this.green * alpha + other.green * otherAlpha * (1 - alpha)) / blendedAlpha
13 | val b = (this.blue * alpha + other.blue * otherAlpha * (1 - alpha)) / blendedAlpha
14 | return Color.fromARGB((blendedAlpha * 255).toInt(), r.toInt(), g.toInt(), b.toInt())
15 | }
16 |
17 | fun Color.lerpRGB(other: Color, t: Double): Color {
18 | return Color.fromARGB(
19 | this.alpha.lerpSafely(other.alpha, t),
20 | this.red.lerpSafely(other.red, t),
21 | this.green.lerpSafely(other.green, t),
22 | this.blue.lerpSafely(other.blue, t),
23 | )
24 | }
25 |
26 | fun Color.toHSV(): Triple {
27 | val r = red / 255.0
28 | val g = green / 255.0
29 | val b = blue / 255.0
30 |
31 | val max = maxOf(r, g, b)
32 | val min = minOf(r, g, b)
33 |
34 | val delta = max - min
35 |
36 | val h = when {
37 | delta == 0.0 -> 0.0
38 | max == r -> 60 * (((g - b) / delta) % 6)
39 | max == g -> 60 * ((b - r) / delta + 2)
40 | max == b -> 60 * ((r - g) / delta + 4)
41 | else -> error("Unreachable")
42 | }
43 |
44 | val s = if (max == 0.0) 0.0 else delta / max
45 | val v = max
46 |
47 | return Triple(h, s, v)
48 | }
49 |
50 | fun List>.interpolate(t: Double, lerpFunction: (Color, Color, Double) -> Color): Color {
51 | val index = this.indexOfLast { it.first <= t }
52 | if (index == this.size - 1) return this.last().second
53 | val start = this[index]
54 | val end = this[index + 1]
55 | // return start.second.lerp(end.second, (t - start.first) / (end.first - start.first))
56 | return lerpFunction(start.second, end.second, (t - start.first) / (end.first - start.first))
57 | }
58 |
59 | fun List>.interpolateRGB(t: Double): Color {
60 | return interpolate(t) { start, end, fraction -> start.lerpRGB(end, fraction) }
61 | }
62 |
63 | fun List>.interpolateOkLab(t: Double): Color {
64 | return interpolate(t) { start, end, fraction -> start.lerpOkLab(end, fraction) }
65 | }
66 |
67 | fun Color.lerpOkLab(other: Color, t: Double): Color {
68 | val start = Oklab.fromRGB(this)
69 | val end = Oklab.fromRGB(other)
70 | val result = start.lerp(end, t).toRGB()
71 | return result
72 | }
73 |
74 | //fun Color.hsvLerp(other: Color, t: Double): Color {
75 | // val (h1, s1, v1) = this.toHSV()
76 | // val (h2, s2, v2) = other.toHSV()
77 | //
78 | // val h = h1.lerp(h2, t)
79 | // val s = s1.lerp(s2, t)
80 | // val v = v1.lerp(v2, t)
81 | // val a = this.alpha.toDouble().lerp(other.alpha.toDouble(), t).toInt()
82 | //
83 | // return hsv(h, s, v).setAlpha(a)
84 | //}
85 |
86 | /**
87 | * Converts an HSV color to RGB.
88 | * @param h The hue in degrees.
89 | * @param s The saturation as a percentage.
90 | * @param v The value as a percentage.
91 | * @return The RGB color.
92 | */
93 | fun hsv(h: Double, s: Double, v: Double): Color {
94 | val c = v * s
95 | val x = c * (1 - abs((h / 60) % 2 - 1))
96 | val m = v - c
97 | val (r, g, b) = when {
98 | h < 60 -> Triple(c, x, .0)
99 | h < 120 -> Triple(x, c, .0)
100 | h < 180 -> Triple(.0, c, x)
101 | h < 240 -> Triple(.0, x, c)
102 | h < 300 -> Triple(x, .0, c)
103 | else -> Triple(c, .0, x)
104 | }
105 | return Color.fromRGB(((r + m) * 255).toInt(), ((g + m) * 255).toInt(), ((b + m) * 255).toInt())
106 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/core.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import net.md_5.bungee.api.ChatMessageType
4 | import org.bukkit.Bukkit
5 | import org.bukkit.Location
6 | import org.bukkit.entity.Player
7 | import org.bukkit.entity.minecart.CommandMinecart
8 | import org.bukkit.plugin.java.JavaPlugin
9 |
10 | lateinit var currentPlugin: JavaPlugin
11 |
12 | private var commandBlockMinecart: CommandMinecart? = null
13 | fun runCommandSilently(command: String, location: Location = Bukkit.getWorlds().first().spawnLocation) {
14 | val server = Bukkit.getServer()
15 |
16 | val commandBlockMinecart = commandBlockMinecart ?: spawnEntity(location, CommandMinecart::class.java) {
17 | commandBlockMinecart = it
18 | it.remove()
19 | }
20 |
21 | server.dispatchCommand(commandBlockMinecart, command)
22 | }
23 |
24 | fun Player.sendActionBar(message: String) {
25 | this.spigot().sendMessage(ChatMessageType.ACTION_BAR, net.md_5.bungee.api.chat.TextComponent(message))
26 | }
27 |
28 | fun sendDebugMessage(message: String) {
29 | // send action bar
30 | Bukkit.getOnlinePlayers().firstOrNull()?.sendActionBar(message)
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/customEntities.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Bukkit
4 | import org.bukkit.entity.Entity
5 |
6 | class EntityTag(
7 | private val tag: String
8 | ) {
9 | fun getEntities() = allEntities().filter { it.scoreboardTags.contains(tag) }
10 |
11 | fun onTick(action: (Entity) -> Unit) {
12 | com.heledron.text_display_experiments.utilities.onTick {
13 | getEntities().forEach { action(it) }
14 | }
15 | }
16 |
17 | fun onInteract(action: (event: org.bukkit.event.player.PlayerInteractEntityEvent) -> Unit) {
18 | onInteractEntity { event ->
19 | if (!event.rightClicked.scoreboardTags.contains(tag)) return@onInteractEntity
20 | action(event)
21 | }
22 | }
23 | }
24 |
25 | fun allEntities() = Bukkit.getServer().worlds.flatMap { it.entities }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/customItems.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Bukkit
4 | import org.bukkit.Bukkit.createInventory
5 | import org.bukkit.ChatColor
6 | import org.bukkit.NamespacedKey
7 | import org.bukkit.entity.Entity
8 | import org.bukkit.entity.Player
9 | import org.bukkit.inventory.ItemStack
10 | import org.bukkit.persistence.PersistentDataType
11 |
12 | val customItemRegistry = mutableListOf()
13 |
14 | fun openCustomItemInventory(player: Player) {
15 | val inventory = createInventory(null, 9 * 3, "Items")
16 | customItemRegistry.forEach { inventory.addItem(it) }
17 | player.openInventory(inventory)
18 | }
19 |
20 | class CustomItemComponent(val id: String) {
21 | fun isItem(item: ItemStack): Boolean {
22 | val key = NamespacedKey(currentPlugin, "item_component_$id")
23 | return item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.BOOLEAN) == true
24 | }
25 |
26 | fun attach(item: ItemStack) {
27 | val itemMeta = item.itemMeta ?: return
28 | val key = NamespacedKey(currentPlugin, "item_component_$id")
29 | itemMeta.persistentDataContainer.set(key, PersistentDataType.BOOLEAN, true)
30 | item.itemMeta = itemMeta
31 | }
32 |
33 | fun onGestureUse(action: (Player, ItemStack) -> Unit) {
34 | onGestureUseItem { player, item ->
35 | if (isItem(item)) action(player, item)
36 | }
37 | }
38 |
39 | fun onInteractEntity(action: (Player, Entity, ItemStack) -> Unit) {
40 | com.heledron.text_display_experiments.utilities.onInteractEntity(fun (player, entity, hand) {
41 | val item = player.inventory.getItem(hand) ?: return
42 | if (isItem(item)) action(player, entity, item)
43 | })
44 | }
45 |
46 | fun onHeldTick(action: (Player, ItemStack) -> Unit) {
47 | onTick {
48 | for (player in Bukkit.getServer().onlinePlayers) {
49 | val itemInMainHand = player.inventory.itemInMainHand
50 | val itemInOffHand = player.inventory.itemInOffHand
51 | if (isItem(itemInMainHand)) action(player, itemInMainHand)
52 | if (isItem(itemInOffHand)) action(player, itemInOffHand)
53 | }
54 | }
55 | }
56 | }
57 |
58 | fun createNamedItem(material: org.bukkit.Material, name: String): ItemStack {
59 | val item = ItemStack(material)
60 | val itemMeta = item.itemMeta ?: throw Exception("ItemMeta is null")
61 | itemMeta.setItemName(ChatColor.RESET.toString() + name)
62 | item.itemMeta = itemMeta
63 | return item
64 | }
65 |
66 | fun ItemStack.attach(component: CustomItemComponent): ItemStack {
67 | component.attach(this)
68 | return this
69 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/events.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.entity.Entity
4 | import org.bukkit.entity.Player
5 | import org.bukkit.event.Listener
6 | import org.bukkit.event.block.Action
7 | import org.bukkit.inventory.EquipmentSlot
8 | import org.bukkit.inventory.ItemStack
9 | import java.io.Closeable
10 |
11 | fun addEventListener(listener: Listener): Closeable {
12 | val plugin = currentPlugin
13 | plugin.server.pluginManager.registerEvents(listener, plugin)
14 | return Closeable {
15 | org.bukkit.event.HandlerList.unregisterAll(listener)
16 | }
17 | }
18 |
19 | fun onInteractEntity(listener: (Player, Entity, EquipmentSlot) -> Unit): Closeable {
20 | return addEventListener(object : Listener {
21 | @org.bukkit.event.EventHandler
22 | fun onInteract(event: org.bukkit.event.player.PlayerInteractEntityEvent) {
23 | listener(event.player, event.rightClicked, event.hand)
24 | }
25 | })
26 | }
27 |
28 |
29 | fun onInteractEntity(listener: (event: org.bukkit.event.player.PlayerInteractEntityEvent) -> Unit): Closeable {
30 | return addEventListener(object : Listener {
31 | @org.bukkit.event.EventHandler
32 | fun onInteract(event: org.bukkit.event.player.PlayerInteractEntityEvent) {
33 | listener(event)
34 | }
35 | })
36 | }
37 |
38 | fun onSpawnEntity(listener: (Entity) -> Unit): Closeable {
39 | return addEventListener(object : Listener {
40 | @org.bukkit.event.EventHandler
41 | fun onSpawn(event: org.bukkit.event.entity.EntitySpawnEvent) {
42 | listener(event.entity)
43 | }
44 | })
45 | }
46 |
47 | fun onGestureUseItem(listener: (Player, ItemStack) -> Unit) = addEventListener(object : Listener {
48 | @org.bukkit.event.EventHandler
49 | fun onPlayerInteract(event: org.bukkit.event.player.PlayerInteractEvent) {
50 | if (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK) return
51 | if (event.action == Action.RIGHT_CLICK_BLOCK && !(event.clickedBlock?.type?.isInteractable == false || event.player.isSneaking)) return
52 | listener(event.player, event.item ?: return)
53 | }
54 | })
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/maths.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Location
4 | import org.bukkit.util.Vector
5 | import org.joml.*
6 | import java.lang.Math
7 | import kotlin.math.abs
8 | import kotlin.math.atan2
9 | import kotlin.math.sign
10 | import kotlin.math.sqrt
11 |
12 | val DOWN_VECTOR; get () = Vector(0, -1, 0)
13 | val UP_VECTOR; get () = Vector(0, 1, 0)
14 | val FORWARD_VECTOR; get () = Vector(0, 0, 1)
15 | val BACKWARD_VECTOR; get () = Vector(0, 0, -1)
16 | val LEFT_VECTOR; get () = Vector(-1, 0, 0)
17 | val RIGHT_VECTOR; get () = Vector(1, 0, 0)
18 |
19 |
20 | fun Vector3f.toVector4f() = Vector4f(x, y, z, 1f)
21 | fun Vector4f.toVector3f() = Vector3f(x, y, z)
22 |
23 | fun Vector.copy(vector: Vector3d): Vector {
24 | this.x = vector.x
25 | this.y = vector.y
26 | this.z = vector.z
27 | return this
28 | }
29 |
30 | fun Vector.copy(vector: Vector3f): Vector {
31 | this.x = vector.x.toDouble()
32 | this.y = vector.y.toDouble()
33 | this.z = vector.z.toDouble()
34 | return this
35 | }
36 |
37 | fun Vector.pitch(): Float {
38 | return atan2(y, sqrt(x * x + z * z)).toFloat()
39 | }
40 |
41 | fun Vector.yaw(): Float {
42 | return -atan2(-x, z).toFloat()
43 | }
44 |
45 | fun Vector.rotate(quaternion: Quaterniond) = copy(Vector3d(x, y, z).rotate(quaternion))
46 |
47 | fun Location.yawRadians(): Float {
48 | return -yaw.toRadians()
49 | }
50 |
51 | fun Location.pitchRadians(): Float {
52 | return pitch.toRadians()
53 | }
54 |
55 | fun Location.getQuaternion(): Quaternionf {
56 | return Quaternionf().rotateYXZ(yawRadians(), pitchRadians(), 0f)
57 | }
58 |
59 | fun Quaterniond.transform(vector: Vector): Vector {
60 | vector.copy(this.transform(vector.toVector3d()))
61 | return vector
62 | }
63 |
64 |
65 | fun Double.lerp(other: Double, t: Double): Double {
66 | return this * (1 - t) + other * t
67 | }
68 |
69 | fun Float.lerp(other: Float, t: Float): Float {
70 | return this * (1 - t) + other * t
71 | }
72 |
73 | fun Int.lerpSafely(other: Int, t: Double): Int {
74 | val result = this.toDouble().lerp(other.toDouble(), t).toInt()
75 | if (result == this && t != .0) return this.moveTowards(other, 1)
76 | return result
77 | }
78 |
79 | fun Double.moveTowards(target: Double, speed: Double): Double {
80 | val distance = target - this
81 | return if (abs(distance) < speed) target else this + speed * distance.sign
82 | }
83 |
84 | fun Float.moveTowards(target: Float, speed: Float): Float {
85 | val distance = target - this
86 | return if (abs(distance) < speed) target else this + speed * distance.sign
87 | }
88 |
89 | fun Int.moveTowards(target: Int, speed: Int): Int {
90 | val distance = target - this
91 | return if (abs(distance) < speed) target else this + speed * distance.sign
92 | }
93 |
94 | fun Double.toRadians(): Double {
95 | return Math.toRadians(this)
96 | }
97 |
98 | fun Float.toRadians(): Float {
99 | return Math.toRadians(this.toDouble()).toFloat()
100 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/oklab.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Color
4 | import kotlin.math.cbrt
5 | import kotlin.math.pow
6 |
7 | class Oklab(
8 | val l: Double,
9 | val a: Double,
10 | val b: Double,
11 | val alpha: Int,
12 | ) {
13 | fun lerp(other: Oklab, t: Double): Oklab {
14 | return Oklab(
15 | l = l.lerp(other.l, t),
16 | a = a.lerp(other.a, t),
17 | b = b.lerp(other.b, t),
18 | alpha = (alpha * (1 - t) + other.alpha * t).toInt(),
19 | )
20 | }
21 |
22 | companion object {
23 | fun fromRGB(color: Color): Oklab {
24 | val r = color.red / 255.0;
25 | val g = color.green / 255.0;
26 | val b = color.blue / 255.0;
27 |
28 | val L = cbrt(
29 | 0.41222147079999993 * r + 0.5363325363 * g + 0.0514459929 * b
30 | )
31 | val M = cbrt(
32 | 0.2119034981999999 * r + 0.6806995450999999 * g + 0.1073969566 * b
33 | );
34 | val S = cbrt(
35 | 0.08830246189999998 * r + 0.2817188376 * g + 0.6299787005000002 * b
36 | );
37 |
38 | return Oklab(
39 | l = 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S,
40 | a = 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S,
41 | b = 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S,
42 | alpha = color.alpha,
43 | )
44 | }
45 | }
46 |
47 | fun toRGB(): Color {
48 | val L = (l * 0.99999999845051981432 +
49 | 0.39633779217376785678 * a +
50 | 0.21580375806075880339 * b).pow(3);
51 | val M = (l * 1.0000000088817607767 -
52 | 0.1055613423236563494 * a -
53 | 0.063854174771705903402 * b).pow(3);
54 | val S = (l * 1.0000000546724109177 -
55 | 0.089484182094965759684 * a -
56 | 1.2914855378640917399 * b).pow(3);
57 |
58 | val r = +4.076741661347994 * L -
59 | 3.307711590408193 * M +
60 | 0.230969928729428 * S
61 | val g = -1.2684380040921763 * L +
62 | 2.6097574006633715 * M -
63 | 0.3413193963102197 * S
64 | val b = -0.004196086541837188 * L -
65 | 0.7034186144594493 * M +
66 | 1.7076147009309444 * S
67 |
68 | return Color.fromARGB(
69 | alpha,
70 | (r.coerceIn(0.0, 1.0) * 255).toInt(),
71 | (g.coerceIn(0.0, 1.0) * 255).toInt(),
72 | (b.coerceIn(0.0, 1.0) * 255).toInt(),
73 | )
74 | }
75 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/overloads.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import org.bukkit.Color
4 | import org.bukkit.Location
5 | import org.bukkit.NamespacedKey
6 | import org.bukkit.Sound
7 | import org.bukkit.entity.Entity
8 | import org.bukkit.persistence.PersistentDataContainer
9 | import org.bukkit.persistence.PersistentDataType
10 |
11 | fun spawnEntity(location: Location, clazz: Class, initializer: (T) -> Unit): T {
12 | return location.world!!.spawn(location, clazz, initializer)
13 | }
14 |
15 | fun playSound(location: Location, sound: Sound, volume: Float, pitch: Float) {
16 | location.world!!.playSound(location, sound, volume, pitch)
17 | }
18 |
19 | fun PersistentDataContainer.getInt(key: NamespacedKey) = this.get(key, PersistentDataType.INTEGER)
20 | fun PersistentDataContainer.getFloat(key: NamespacedKey) = this.get(key, PersistentDataType.FLOAT)
21 | fun PersistentDataContainer.getDouble(key: NamespacedKey) = this.get(key, PersistentDataType.DOUBLE)
22 |
23 | fun PersistentDataContainer.getString(key: NamespacedKey) = this.get(key, PersistentDataType.STRING)
24 | fun PersistentDataContainer.setString(key: NamespacedKey, value: String) = this.set(key, PersistentDataType.STRING, value)
25 |
26 | fun PersistentDataContainer.getColor(key: NamespacedKey): Color? {
27 | val string = getString(key) ?: return null
28 | try {
29 | val parsed = string.toLongOrNull(radix = 16)?.toInt() ?: return null
30 | return Color.fromARGB(parsed)
31 | } catch (e: NumberFormatException) {
32 | return null
33 | } catch (e: IllegalArgumentException) {
34 | return null
35 | }
36 | }
37 |
38 | fun PersistentDataContainer.setColor(key: NamespacedKey, value: Color) {
39 | fun toHex(value: Int) = value.toString(16).padStart(2, '0')
40 |
41 | val string = "${toHex(value.alpha)}${toHex(value.red)}${toHex(value.green)}${toHex(value.blue)}"
42 | setString(key, string)
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/pointDetection.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import com.heledron.text_display_experiments.utilities.rendering.RenderEntity
4 | import com.heledron.text_display_experiments.utilities.rendering.SharedEntityRenderer
5 | import org.bukkit.entity.Interaction
6 | import org.bukkit.entity.Player
7 | import org.bukkit.util.Vector
8 | import org.joml.*
9 | import java.util.WeakHashMap
10 |
11 | private fun lineAtZ(point1: Vector3f, point2: Vector3f, z: Float): Vector3f {
12 | val t = (z - point1.z) / (point2.z - point1.z)
13 | return Vector3f(point1).lerp(point2, t)
14 | }
15 |
16 |
17 | private fun lineIntersectsPlane(
18 | point1: Vector3f,
19 | point2: Vector3f,
20 | planeTransform: Matrix4f,
21 | xRange: ClosedRange,
22 | yRange: ClosedRange,
23 | ): Boolean {
24 | val inverted = Matrix4f(planeTransform).invert()
25 |
26 | val point1Transformed = inverted.transform(point1.toVector4f()).toVector3f()
27 | val point2Transformed = inverted.transform(point2.toVector4f()).toVector3f()
28 |
29 | val point = lineAtZ(point1Transformed, point2Transformed, 0f)
30 |
31 | return point.y in yRange && point.x in xRange
32 | }
33 |
34 | class PlanePointDetector(
35 | players: List,
36 | val displayPosition: Vector,
37 | val xRange: ClosedRange = 0f..1f,
38 | val yRange: ClosedRange = 0f..1f,
39 | ) {
40 | val points = players.map { player ->
41 | val location = player.eyeLocation
42 | val point1 = location.toVector().subtract(displayPosition).toVector3f()
43 | val point2 = location.toVector().add(location.direction).subtract(displayPosition).toVector3f()
44 | Triple(player, point1, point2)
45 | }
46 |
47 | fun lookingAt(planeTransform: Matrix4f) = points.filter { (_, point1, point2) ->
48 | lineIntersectsPlane(point1, point2, planeTransform, yRange, xRange)
49 | }.map { (player, _, _) -> player }
50 |
51 | fun detectClick(planeTransform: Matrix4f) = lookingAt(planeTransform).map { player ->
52 | SharedEntityRenderer.render("point_detector" to player, RenderEntity(
53 | clazz = Interaction::class.java,
54 | location = player.location,
55 | init = {
56 | it.interactionWidth = player.width.toFloat()
57 | it.interactionHeight = player.height.toFloat()
58 | it.scoreboardTags.add("plane_point_detector")
59 | },
60 | ))
61 |
62 | ClickDetectionResult(player, didClick[player] ?: false)
63 | }
64 |
65 | companion object {
66 | private var didClick = WeakHashMap()
67 |
68 | init {
69 | onTickEnd {
70 | didClick.clear()
71 | }
72 | onInteractEntity { player, entity, _ ->
73 | if (!entity.scoreboardTags.contains("plane_point_detector")) return@onInteractEntity
74 | didClick[player] = true
75 | }
76 | }
77 | }
78 | }
79 |
80 | class ClickDetectionResult(
81 | val player: Player,
82 | val isClicked: Boolean,
83 | )
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/rendering/EntityRenderer.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities.rendering
2 |
3 | import com.heledron.text_display_experiments.utilities.*
4 | import org.bukkit.Location
5 | import org.bukkit.World
6 | import org.bukkit.entity.BlockDisplay
7 | import org.bukkit.entity.Entity
8 | import org.bukkit.entity.TextDisplay
9 | import org.bukkit.util.Vector
10 | import java.io.Closeable
11 |
12 | class RenderEntity (
13 | val clazz : Class,
14 | val location : Location,
15 | val init : (T) -> Unit = {},
16 | val preUpdate : (T) -> Unit = {},
17 | val update : (T) -> Unit = {},
18 | )
19 |
20 | class RenderEntityGroup {
21 | val items = mutableMapOf>()
22 |
23 | fun add(id: Any, item: RenderEntity<*>) {
24 | items[id] = item
25 | }
26 |
27 | fun add(id: Any, item: RenderEntityGroup) {
28 | for ((subId, part) in item.items) {
29 | items[id to subId] = part
30 | }
31 | }
32 | }
33 |
34 | fun blockRenderEntity(
35 | location: Location,
36 | init: (BlockDisplay) -> Unit = {},
37 | update: (BlockDisplay) -> Unit = {}
38 | ) = RenderEntity(
39 | clazz = BlockDisplay::class.java,
40 | location = location,
41 | init = init,
42 | update = update
43 | )
44 |
45 | fun blockRenderEntity(
46 | world: World,
47 | position: Vector,
48 | init: (BlockDisplay) -> Unit = {},
49 | update: (BlockDisplay) -> Unit = {}
50 | ) = RenderEntity(
51 | clazz = BlockDisplay::class.java,
52 | location = position.toLocation(world),
53 | init = init,
54 | update = update,
55 | )
56 |
57 | //fun lineRenderEntity(
58 | // world: World,
59 | // position: Vector,
60 | // vector: Vector,
61 | // upVector: Vector = if (vector.x + vector.z != 0.0) UP_VECTOR else FORWARD_VECTOR,
62 | // thickness: Float = .1f,
63 | // interpolation: Int = 1,
64 | // init: (BlockDisplay) -> Unit = {},
65 | // update: (BlockDisplay) -> Unit = {}
66 | //) = blockRenderEntity(
67 | // world = world,
68 | // position = position,
69 | // init = {
70 | // it.teleportDuration = interpolation
71 | // it.interpolationDuration = interpolation
72 | // init(it)
73 | // },
74 | // update = {
75 | // val matrix = Matrix4f().rotateTowards(vector.toVector3f(), upVector.toVector3f())
76 | // .translate(-thickness / 2, -thickness / 2, 0f)
77 | // .scale(thickness, thickness, vector.length().toFloat())
78 | //
79 | // it.applyTransformationWithInterpolation(matrix)
80 | // update(it)
81 | // }
82 | //)
83 |
84 | fun textRenderEntity(
85 | location: Location,
86 | init: (TextDisplay) -> Unit = {},
87 | preUpdate: (TextDisplay) -> Unit = {},
88 | update: (TextDisplay) -> Unit = {},
89 | ) = RenderEntity(
90 | clazz = TextDisplay::class.java,
91 | location = location,
92 | init = {
93 | init(it)
94 | },
95 | preUpdate = {
96 | preUpdate(it)
97 | },
98 | update = {
99 | update(it)
100 | }
101 | )
102 |
103 | fun textRenderEntity(
104 | world: World,
105 | position: Vector,
106 | init: (TextDisplay) -> Unit = {},
107 | preUpdate: (TextDisplay) -> Unit = {},
108 | update: (TextDisplay) -> Unit = {},
109 | ) = textRenderEntity(
110 | location = position.toLocation(world),
111 | init = init,
112 | preUpdate = preUpdate,
113 | update = update
114 | )
115 |
116 | class SingleEntityRenderer: Closeable {
117 | var entity: T? = null
118 |
119 | fun render(part: RenderEntity) {
120 | entity = (entity ?: spawnEntity(part.location, part.clazz) {
121 | part.init(it)
122 | }).apply {
123 | this.teleport(part.location)
124 | part.preUpdate(this)
125 | part.update(this)
126 | }
127 | }
128 |
129 | override fun close() {
130 | entity?.remove()
131 | entity = null
132 | }
133 | }
134 |
135 | class GroupEntityRenderer: Closeable {
136 | val rendered = mutableMapOf()
137 |
138 | private val used = mutableSetOf()
139 |
140 | fun detachEntity(id: Any) {
141 | rendered.remove(id)
142 | }
143 |
144 | override fun close() {
145 | for (entity in rendered.values) {
146 | entity.remove()
147 | }
148 | rendered.clear()
149 | used.clear()
150 | }
151 |
152 | fun render(group: RenderEntityGroup) {
153 |
154 | @Suppress("UNCHECKED_CAST")
155 | fun update(renderEntity: RenderEntity, entity: Entity) = renderEntity.update(entity as T)
156 |
157 | @Suppress("UNCHECKED_CAST")
158 | fun preUpdate(renderEntity: RenderEntity, entity: Entity) = renderEntity.preUpdate(entity as T)
159 |
160 | for ((id, template) in group.items) renderPart(id, template)
161 | for ((id, template) in group.items) preUpdate(template, rendered[id]!!)
162 | for ((id, template) in group.items) update(template, rendered[id]!!)
163 |
164 | val toRemove = rendered.keys - used
165 | for (key in toRemove) {
166 | val entity = rendered[key]!!
167 | entity.remove()
168 | rendered.remove(key)
169 | }
170 | used.clear()
171 | }
172 |
173 |
174 | fun render(part: RenderEntity) {
175 | render(RenderEntityGroup().apply { add(0, part) })
176 | }
177 |
178 | private fun renderPart(id: Any, template: RenderEntity) {
179 | used.add(id)
180 |
181 | val oldEntity = rendered[id]
182 | if (oldEntity != null) {
183 | // check if the entity is of the same type
184 | if (oldEntity.type.entityClass == template.clazz) {
185 | oldEntity.teleport(template.location)
186 | return
187 | }
188 |
189 | oldEntity.remove()
190 | rendered.remove(id)
191 | }
192 |
193 | val entity = spawnEntity(template.location, template.clazz) {
194 | template.init(it)
195 | }
196 | rendered[id] = entity
197 | }
198 | }
199 |
200 |
201 | object SharedEntityRenderer {
202 | private val renderer = GroupEntityRenderer()
203 | private var group = RenderEntityGroup()
204 |
205 | fun render(id: Any, group: RenderEntityGroup) {
206 | this.group.add(id, group)
207 | }
208 |
209 | fun render(id: Any, entity: RenderEntity) {
210 | this.group.add(id, entity)
211 | }
212 |
213 | fun flush() {
214 | renderer.render(group)
215 | group = RenderEntityGroup()
216 | }
217 |
218 | val rendered: Map get() = renderer.rendered
219 |
220 | fun detach(id: Any) {
221 | renderer.detachEntity(id)
222 | }
223 |
224 | init {
225 | onTickEnd {
226 | flush()
227 | }
228 |
229 | onDisablePlugin {
230 | renderer.close()
231 | }
232 | }
233 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/rendering/utilities.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities.rendering
2 |
3 | import org.bukkit.entity.Display
4 | import org.bukkit.util.Transformation
5 | import org.joml.Matrix4f
6 |
7 |
8 | fun Display.interpolateTransform(transformation: Transformation) {
9 | if (this.transformation == transformation) return
10 | this.transformation = transformation
11 | this.interpolationDelay = 0
12 | }
13 |
14 | fun Display.interpolateTransform(matrix: Matrix4f) {
15 | val oldTransform = this.transformation
16 | setTransformationMatrix(matrix)
17 |
18 | if (oldTransform == this.transformation) return
19 | this.interpolationDelay = 0
20 | }
--------------------------------------------------------------------------------
/src/main/java/com/heledron/text_display_experiments/utilities/scheduler.kt:
--------------------------------------------------------------------------------
1 | package com.heledron.text_display_experiments.utilities
2 |
3 | import java.io.Closeable
4 |
5 | fun runLater(delay: Long, task: () -> Unit): Closeable {
6 | val plugin = currentPlugin
7 | val handler = plugin.server.scheduler.runTaskLater(plugin, task, delay)
8 | return Closeable {
9 | handler.cancel()
10 | }
11 | }
12 |
13 | fun interval(delay: Long, period: Long, task: (it: Closeable) -> Unit): Closeable {
14 | val plugin = currentPlugin
15 | lateinit var handler: org.bukkit.scheduler.BukkitTask
16 | val closeable = Closeable { handler.cancel() }
17 | handler = plugin.server.scheduler.runTaskTimer(plugin, Runnable { task(closeable) }, delay, period)
18 | return closeable
19 | }
20 |
21 | fun onTick(task: (it: Closeable) -> Unit) = TickSchedule.schedule(TickSchedule.main, task)
22 | fun onTickEnd(task: (it: Closeable) -> Unit) = TickSchedule.schedule(TickSchedule.end, task)
23 |
24 |
25 | private val closeableList = mutableListOf<()->Unit>()
26 | fun onDisablePlugin(task: () -> Unit) = closeableList.add(task)
27 | fun closeCurrentPlugin() {
28 | closeableList.forEach { it() }
29 | }
30 |
31 |
32 | private object TickSchedule {
33 | val main = mutableListOf<() -> Unit>()
34 | val end = mutableListOf<() -> Unit>()
35 |
36 | fun schedule(list: MutableList<() -> Unit>, task: (it: Closeable) -> Unit): Closeable {
37 | lateinit var closeable: Closeable
38 |
39 | val handler = { task(closeable) }
40 | closeable = Closeable { list.remove(handler) }
41 |
42 | list.add(handler)
43 |
44 | return closeable
45 | }
46 |
47 |
48 | init {
49 | interval(0,1) {
50 | main.forEach { it() }
51 | end.forEach { it() }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/main/resources/block_colors.json:
--------------------------------------------------------------------------------
1 | {
2 | "acacia_door": [121, 68, 44],
3 | "acacia_leaves": [86, 86, 86],
4 | "acacia_log": [103, 97, 87],
5 | "acacia_planks": [168, 90, 50],
6 | "acacia_sapling": [51, 51, 10],
7 | "acacia_trapdoor": [120, 67, 39],
8 | "activator_rail": [70, 53, 45],
9 | "allium": [24, 20, 27],
10 | "amethyst_block": [134, 98, 191],
11 | "amethyst_cluster": [90, 69, 113],
12 | "ancient_debris": [95, 66, 58],
13 | "andesite": [136, 136, 137],
14 | "anvil": [69, 69, 69],
15 | "attached_melon_stem": [18, 18, 18],
16 | "attached_pumpkin_stem": [18, 18, 18],
17 | "azalea": [102, 125, 48],
18 | "azalea_leaves": [69, 88, 34],
19 | "azure_bluet": [28, 34, 21],
20 | "bamboo_block": [127, 144, 58],
21 | "bamboo_door": [155, 139, 66],
22 | "bamboo_fence": [138, 124, 60],
23 | "bamboo_fence_gate": [155, 139, 65],
24 | "bamboo_mosaic": [190, 170, 78],
25 | "bamboo_planks": [193, 173, 80],
26 | "bamboo_trapdoor": [165, 148, 71],
27 | "barrel": [135, 101, 58],
28 | "basalt": [81, 81, 86],
29 | "beacon": [118, 221, 215],
30 | "bedrock": [85, 85, 85],
31 | "bee_nest": [202, 160, 75],
32 | "bell": [63, 59, 28],
33 | "big_dripleaf": [101, 129, 47],
34 | "big_dripleaf_stem": [30, 38, 15],
35 | "birch_door": [220, 210, 176],
36 | "birch_leaves": [73, 73, 73],
37 | "birch_log": [217, 215, 210],
38 | "birch_planks": [192, 175, 121],
39 | "birch_sapling": [49, 62, 30],
40 | "birch_trapdoor": [207, 194, 157],
41 | "black_candle": [3, 3, 5],
42 | "black_concrete": [8, 10, 15],
43 | "black_concrete_powder": [25, 27, 32],
44 | "black_glazed_terracotta": [68, 30, 32],
45 | "black_shulker_box": [25, 25, 30],
46 | "black_stained_glass": [25, 25, 25],
47 | "black_stained_glass_pane": [3, 3, 3],
48 | "black_terracotta": [37, 23, 16],
49 | "black_wool": [21, 21, 26],
50 | "blackstone": [42, 36, 41],
51 | "blast_furnace": [81, 80, 81],
52 | "blue_candle": [5, 6, 13],
53 | "blue_concrete": [45, 47, 143],
54 | "blue_concrete_powder": [70, 73, 167],
55 | "blue_glazed_terracotta": [47, 65, 139],
56 | "blue_ice": [116, 168, 253],
57 | "blue_orchid": [8, 28, 29],
58 | "blue_shulker_box": [44, 46, 140],
59 | "blue_stained_glass": [50, 75, 177],
60 | "blue_stained_glass_pane": [6, 9, 21],
61 | "blue_terracotta": [74, 60, 91],
62 | "blue_wool": [53, 57, 157],
63 | "bone_block": [210, 206, 179],
64 | "bookshelf": [117, 95, 60],
65 | "brain_coral": [86, 37, 67],
66 | "brain_coral_block": [207, 91, 159],
67 | "brain_coral_fan": [75, 31, 57],
68 | "brewing_stand": [62, 51, 41],
69 | "bricks": [151, 98, 83],
70 | "brown_candle": [9, 6, 3],
71 | "brown_concrete": [96, 60, 32],
72 | "brown_concrete_powder": [126, 85, 54],
73 | "brown_glazed_terracotta": [120, 106, 86],
74 | "brown_mushroom": [20, 16, 12],
75 | "brown_mushroom_block": [149, 112, 81],
76 | "brown_shulker_box": [106, 66, 36],
77 | "brown_stained_glass": [102, 75, 50],
78 | "brown_stained_glass_pane": [12, 9, 6],
79 | "brown_terracotta": [77, 51, 36],
80 | "brown_wool": [114, 72, 41],
81 | "bubble_coral": [84, 12, 83],
82 | "bubble_coral_block": [165, 26, 162],
83 | "bubble_coral_fan": [70, 14, 70],
84 | "budding_amethyst": [132, 96, 187],
85 | "cactus": [66, 97, 33],
86 | "cake": [190, 170, 164],
87 | "calcite": [223, 224, 221],
88 | "calibrated_sculk_sensor": [28, 79, 101],
89 | "candle": [19, 16, 13],
90 | "cartography_table": [103, 87, 67],
91 | "carved_pumpkin": [150, 84, 17],
92 | "cauldron": [45, 44, 45],
93 | "cave_vines": [51, 61, 23],
94 | "cave_vines_plant": [50, 57, 21],
95 | "chain": [11, 13, 16],
96 | "cherry_door": [192, 147, 142],
97 | "cherry_leaves": [194, 146, 164],
98 | "cherry_log": [55, 33, 44],
99 | "cherry_planks": [227, 179, 173],
100 | "cherry_sapling": [78, 56, 68],
101 | "cherry_trapdoor": [195, 153, 148],
102 | "chipped_anvil": [46, 46, 46],
103 | "chiseled_bookshelf": [178, 145, 89],
104 | "chiseled_copper": [184, 101, 74],
105 | "chiseled_deepslate": [54, 54, 55],
106 | "chiseled_nether_bricks": [47, 24, 28],
107 | "chiseled_polished_blackstone": [54, 49, 57],
108 | "chiseled_quartz_block": [232, 227, 218],
109 | "chiseled_red_sandstone": [183, 97, 28],
110 | "chiseled_sandstone": [216, 203, 155],
111 | "chiseled_stone_bricks": [120, 119, 120],
112 | "chiseled_tuff": [89, 94, 87],
113 | "chiseled_tuff_bricks": [99, 103, 96],
114 | "chorus_flower": [151, 121, 152],
115 | "chorus_plant": [94, 57, 94],
116 | "clay": [161, 166, 179],
117 | "coal_block": [16, 16, 16],
118 | "coal_ore": [106, 106, 105],
119 | "coarse_dirt": [119, 86, 59],
120 | "cobbled_deepslate": [77, 77, 81],
121 | "cobblestone": [128, 127, 128],
122 | "cobweb": [94, 96, 96],
123 | "comparator": [166, 162, 160],
124 | "composter": [67, 43, 23],
125 | "conduit": [90, 79, 64],
126 | "copper_block": [192, 108, 80],
127 | "copper_bulb": [156, 87, 57],
128 | "copper_door": [157, 89, 67],
129 | "copper_grate": [134, 75, 56],
130 | "copper_ore": [125, 126, 120],
131 | "copper_trapdoor": [152, 85, 63],
132 | "cornflower": [11, 17, 20],
133 | "cracked_deepslate_bricks": [65, 65, 65],
134 | "cracked_deepslate_tiles": [53, 53, 53],
135 | "cracked_nether_bricks": [40, 20, 24],
136 | "cracked_polished_blackstone_bricks": [44, 38, 44],
137 | "cracked_stone_bricks": [118, 118, 118],
138 | "crafter": [112, 99, 100],
139 | "crafting_table": [120, 73, 42],
140 | "creaking_heart": [82, 68, 63],
141 | "crimson_door": [114, 55, 79],
142 | "crimson_fungus": [33, 10, 7],
143 | "crimson_nylium": [131, 31, 31],
144 | "crimson_planks": [101, 49, 71],
145 | "crimson_roots": [45, 3, 15],
146 | "crimson_stem": [93, 26, 30],
147 | "crimson_trapdoor": [88, 43, 61],
148 | "crying_obsidian": [33, 10, 60],
149 | "cut_copper": [191, 107, 81],
150 | "cut_red_sandstone": [189, 102, 32],
151 | "cut_sandstone": [218, 206, 160],
152 | "cyan_candle": [1, 10, 10],
153 | "cyan_concrete": [21, 119, 136],
154 | "cyan_concrete_powder": [37, 148, 157],
155 | "cyan_glazed_terracotta": [52, 119, 125],
156 | "cyan_shulker_box": [20, 121, 135],
157 | "cyan_stained_glass": [75, 127, 152],
158 | "cyan_stained_glass_pane": [9, 15, 18],
159 | "cyan_terracotta": [87, 91, 91],
160 | "cyan_wool": [21, 138, 145],
161 | "damaged_anvil": [45, 45, 45],
162 | "dandelion": [18, 22, 5],
163 | "dark_oak_door": [77, 52, 25],
164 | "dark_oak_leaves": [105, 106, 105],
165 | "dark_oak_log": [60, 47, 26],
166 | "dark_oak_planks": [67, 43, 20],
167 | "dark_oak_sapling": [26, 39, 13],
168 | "dark_oak_trapdoor": [75, 50, 23],
169 | "dark_prismarine": [52, 92, 76],
170 | "daylight_detector": [131, 116, 95],
171 | "dead_brain_coral": [59, 55, 53],
172 | "dead_brain_coral_block": [124, 118, 114],
173 | "dead_brain_coral_fan": [49, 46, 45],
174 | "dead_bubble_coral": [69, 65, 63],
175 | "dead_bubble_coral_block": [132, 124, 119],
176 | "dead_bubble_coral_fan": [62, 59, 57],
177 | "dead_bush": [33, 24, 12],
178 | "dead_fire_coral": [58, 55, 53],
179 | "dead_fire_coral_block": [132, 124, 120],
180 | "dead_fire_coral_fan": [51, 48, 47],
181 | "dead_horn_coral": [56, 53, 51],
182 | "dead_horn_coral_block": [134, 126, 122],
183 | "dead_horn_coral_fan": [49, 46, 45],
184 | "dead_tube_coral": [71, 66, 65],
185 | "dead_tube_coral_block": [130, 123, 120],
186 | "dead_tube_coral_fan": [40, 38, 37],
187 | "deepslate": [80, 80, 83],
188 | "deepslate_bricks": [71, 71, 71],
189 | "deepslate_coal_ore": [74, 74, 76],
190 | "deepslate_copper_ore": [92, 93, 89],
191 | "deepslate_diamond_ore": [83, 106, 107],
192 | "deepslate_emerald_ore": [78, 104, 88],
193 | "deepslate_gold_ore": [115, 103, 78],
194 | "deepslate_iron_ore": [107, 100, 95],
195 | "deepslate_lapis_ore": [80, 91, 115],
196 | "deepslate_redstone_ore": [105, 73, 75],
197 | "deepslate_tiles": [55, 55, 55],
198 | "detector_rail": [75, 64, 55],
199 | "diamond_block": [98, 237, 228],
200 | "diamond_ore": [121, 141, 141],
201 | "diorite": [189, 188, 189],
202 | "dirt": [134, 96, 67],
203 | "dirt_path": [148, 122, 65],
204 | "dragon_egg": [13, 9, 16],
205 | "dripstone_block": [134, 108, 93],
206 | "emerald_block": [42, 203, 88],
207 | "emerald_ore": [108, 136, 116],
208 | "enchanting_table": [129, 75, 85],
209 | "end_portal_frame": [91, 121, 97],
210 | "end_rod": [47, 45, 42],
211 | "end_stone": [220, 223, 158],
212 | "end_stone_bricks": [218, 224, 162],
213 | "exposed_chiseled_copper": [155, 119, 101],
214 | "exposed_copper": [161, 126, 104],
215 | "exposed_copper_bulb": [135, 108, 90],
216 | "exposed_copper_door": [133, 100, 87],
217 | "exposed_copper_grate": [113, 88, 73],
218 | "exposed_copper_trapdoor": [128, 100, 83],
219 | "exposed_cut_copper": [155, 122, 101],
220 | "farmland": [143, 103, 71],
221 | "fern": [43, 43, 43],
222 | "fire_coral": [71, 16, 20],
223 | "fire_coral_block": [164, 35, 47],
224 | "fire_coral_fan": [65, 14, 19],
225 | "fletching_table": [197, 180, 133],
226 | "flower_pot": [24, 13, 10],
227 | "flowering_azalea": [112, 122, 64],
228 | "flowering_azalea_leaves": [80, 89, 49],
229 | "frogspawn": [35, 30, 28],
230 | "furnace": [110, 110, 110],
231 | "gilded_blackstone": [56, 43, 38],
232 | "glass": [45, 54, 56],
233 | "glass_pane": [21, 26, 27],
234 | "glow_lichen": [46, 54, 50],
235 | "glowstone": [172, 131, 84],
236 | "gold_block": [246, 208, 62],
237 | "gold_ore": [145, 134, 107],
238 | "granite": [149, 103, 86],
239 | "gravel": [132, 127, 127],
240 | "gray_candle": [7, 8, 8],
241 | "gray_concrete": [55, 58, 62],
242 | "gray_concrete_powder": [77, 81, 85],
243 | "gray_glazed_terracotta": [83, 90, 94],
244 | "gray_shulker_box": [55, 59, 62],
245 | "gray_stained_glass": [75, 75, 75],
246 | "gray_stained_glass_pane": [9, 9, 9],
247 | "gray_terracotta": [58, 42, 36],
248 | "gray_wool": [63, 68, 72],
249 | "green_candle": [6, 8, 2],
250 | "green_concrete": [73, 91, 36],
251 | "green_concrete_powder": [97, 119, 45],
252 | "green_glazed_terracotta": [117, 142, 67],
253 | "green_shulker_box": [79, 101, 32],
254 | "green_stained_glass": [102, 127, 50],
255 | "green_stained_glass_pane": [12, 15, 6],
256 | "green_terracotta": [76, 83, 42],
257 | "green_wool": [85, 110, 28],
258 | "hanging_roots": [47, 34, 27],
259 | "hay_block": [166, 139, 12],
260 | "heavy_core": [62, 65, 71],
261 | "honey_block": [251, 186, 53],
262 | "honeycomb_block": [229, 148, 30],
263 | "hopper": [46, 45, 46],
264 | "horn_coral": [82, 73, 25],
265 | "horn_coral_block": [216, 200, 66],
266 | "horn_coral_fan": [76, 67, 22],
267 | "ice": [146, 184, 254],
268 | "iron_bars": [62, 63, 61],
269 | "iron_block": [220, 220, 220],
270 | "iron_door": [158, 157, 157],
271 | "iron_ore": [136, 129, 123],
272 | "iron_trapdoor": [174, 174, 174],
273 | "jack_o_lantern": [215, 152, 53],
274 | "jigsaw": [80, 70, 81],
275 | "jukebox": [94, 64, 47],
276 | "jungle_door": [144, 106, 75],
277 | "jungle_leaves": [109, 107, 100],
278 | "jungle_log": [85, 68, 25],
279 | "jungle_planks": [160, 115, 81],
280 | "jungle_sapling": [16, 27, 6],
281 | "jungle_trapdoor": [128, 92, 65],
282 | "kelp": [16, 26, 8],
283 | "kelp_plant": [34, 51, 17],
284 | "ladder": [70, 54, 31],
285 | "lantern": [44, 38, 35],
286 | "lapis_block": [31, 67, 140],
287 | "lapis_ore": [107, 118, 141],
288 | "large_amethyst_bud": [49, 39, 62],
289 | "large_fern": [51, 51, 51],
290 | "lectern": [174, 138, 83],
291 | "lever": [9, 7, 5],
292 | "light_blue_candle": [3, 11, 16],
293 | "light_blue_concrete": [36, 137, 199],
294 | "light_blue_concrete_powder": [74, 181, 213],
295 | "light_blue_glazed_terracotta": [95, 165, 209],
296 | "light_blue_shulker_box": [49, 164, 212],
297 | "light_blue_stained_glass": [102, 152, 215],
298 | "light_blue_stained_glass_pane": [12, 18, 26],
299 | "light_blue_terracotta": [113, 109, 138],
300 | "light_blue_wool": [58, 175, 217],
301 | "light_gray_candle": [10, 10, 9],
302 | "light_gray_concrete": [125, 125, 115],
303 | "light_gray_concrete_powder": [155, 155, 148],
304 | "light_gray_glazed_terracotta": [144, 166, 168],
305 | "light_gray_shulker_box": [124, 124, 115],
306 | "light_gray_stained_glass": [152, 152, 152],
307 | "light_gray_stained_glass_pane": [18, 18, 18],
308 | "light_gray_terracotta": [135, 107, 98],
309 | "light_gray_wool": [142, 142, 135],
310 | "lightning_rod": [31, 17, 13],
311 | "lilac": [45, 37, 43],
312 | "lily_of_the_valley": [25, 35, 19],
313 | "lily_pad": [78, 78, 78],
314 | "lime_candle": [8, 14, 2],
315 | "lime_concrete": [94, 169, 25],
316 | "lime_concrete_powder": [125, 189, 42],
317 | "lime_glazed_terracotta": [163, 198, 55],
318 | "lime_shulker_box": [100, 173, 23],
319 | "lime_stained_glass": [127, 205, 25],
320 | "lime_stained_glass_pane": [15, 25, 3],
321 | "lime_terracotta": [104, 118, 53],
322 | "lime_wool": [112, 185, 26],
323 | "lodestone": [147, 149, 153],
324 | "loom": [142, 119, 92],
325 | "magenta_candle": [13, 4, 13],
326 | "magenta_concrete": [169, 48, 159],
327 | "magenta_concrete_powder": [193, 84, 185],
328 | "magenta_glazed_terracotta": [208, 100, 192],
329 | "magenta_shulker_box": [174, 54, 164],
330 | "magenta_stained_glass": [177, 75, 215],
331 | "magenta_stained_glass_pane": [21, 9, 26],
332 | "magenta_terracotta": [150, 88, 109],
333 | "magenta_wool": [190, 69, 180],
334 | "mangrove_door": [113, 49, 47],
335 | "mangrove_leaves": [107, 106, 106],
336 | "mangrove_log": [84, 67, 41],
337 | "mangrove_planks": [118, 54, 49],
338 | "mangrove_propagule": [12, 23, 11],
339 | "mangrove_roots": [52, 41, 27],
340 | "mangrove_trapdoor": [97, 41, 37],
341 | "medium_amethyst_bud": [32, 24, 40],
342 | "melon": [111, 145, 31],
343 | "melon_stem": [21, 21, 21],
344 | "moss_block": [89, 110, 45],
345 | "mossy_cobblestone": [110, 119, 95],
346 | "mossy_stone_bricks": [115, 121, 105],
347 | "mud": [60, 57, 61],
348 | "mud_bricks": [137, 104, 79],
349 | "muddy_mangrove_roots": [70, 59, 45],
350 | "mushroom_stem": [203, 197, 186],
351 | "mycelium": [111, 99, 101],
352 | "nether_bricks": [44, 22, 26],
353 | "nether_gold_ore": [115, 55, 42],
354 | "nether_portal": [87, 11, 191],
355 | "nether_quartz_ore": [118, 66, 62],
356 | "nether_sprouts": [2, 18, 16],
357 | "nether_wart_block": [115, 3, 2],
358 | "netherite_block": [67, 61, 64],
359 | "netherrack": [98, 38, 38],
360 | "note_block": [89, 59, 41],
361 | "oak_door": [114, 90, 54],
362 | "oak_leaves": [97, 97, 97],
363 | "oak_log": [109, 85, 51],
364 | "oak_planks": [162, 131, 79],
365 | "oak_sapling": [34, 46, 18],
366 | "oak_trapdoor": [107, 85, 49],
367 | "observer": [98, 98, 98],
368 | "obsidian": [15, 11, 25],
369 | "ochre_froglight": [251, 245, 207],
370 | "orange_candle": [18, 8, 1],
371 | "orange_concrete": [224, 97, 1],
372 | "orange_concrete_powder": [227, 132, 32],
373 | "orange_glazed_terracotta": [155, 147, 92],
374 | "orange_shulker_box": [234, 106, 9],
375 | "orange_stained_glass": [215, 127, 50],
376 | "orange_stained_glass_pane": [26, 15, 6],
377 | "orange_terracotta": [162, 84, 38],
378 | "orange_tulip": [15, 23, 5],
379 | "orange_wool": [241, 118, 20],
380 | "oxeye_daisy": [36, 40, 29],
381 | "oxidized_chiseled_copper": [84, 162, 132],
382 | "oxidized_copper": [82, 163, 133],
383 | "oxidized_copper_bulb": [70, 132, 109],
384 | "oxidized_copper_door": [67, 130, 108],
385 | "oxidized_copper_grate": [57, 113, 92],
386 | "oxidized_copper_trapdoor": [67, 129, 105],
387 | "oxidized_cut_copper": [80, 154, 126],
388 | "packed_ice": [142, 180, 250],
389 | "packed_mud": [142, 107, 80],
390 | "pale_hanging_moss": [47, 49, 47],
391 | "pale_moss_block": [107, 112, 105],
392 | "pale_moss_carpet": [107, 112, 105],
393 | "pale_oak_door": [216, 208, 207],
394 | "pale_oak_leaves": [95, 99, 93],
395 | "pale_oak_log": [88, 78, 75],
396 | "pale_oak_planks": [228, 218, 216],
397 | "pale_oak_sapling": [44, 43, 40],
398 | "pale_oak_trapdoor": [230, 220, 219],
399 | "pearlescent_froglight": [246, 240, 240],
400 | "peony": [56, 54, 60],
401 | "pink_candle": [17, 8, 12],
402 | "pink_concrete": [214, 101, 143],
403 | "pink_concrete_powder": [229, 153, 181],
404 | "pink_glazed_terracotta": [235, 155, 182],
405 | "pink_petals": [99, 73, 88],
406 | "pink_shulker_box": [230, 122, 158],
407 | "pink_stained_glass": [242, 127, 165],
408 | "pink_stained_glass_pane": [29, 15, 20],
409 | "pink_terracotta": [162, 78, 79],
410 | "pink_tulip": [16, 25, 13],
411 | "pink_wool": [238, 141, 172],
412 | "piston": [153, 128, 85],
413 | "pitcher_crop": [76, 65, 40],
414 | "podzol": [92, 63, 24],
415 | "polished_andesite": [132, 135, 134],
416 | "polished_basalt": [99, 99, 101],
417 | "polished_blackstone": [53, 49, 57],
418 | "polished_blackstone_bricks": [48, 43, 50],
419 | "polished_deepslate": [72, 73, 73],
420 | "polished_diorite": [193, 193, 195],
421 | "polished_granite": [154, 107, 89],
422 | "polished_tuff": [98, 104, 100],
423 | "poppy": [18, 9, 5],
424 | "potted_azalea_bush": [25, 31, 12],
425 | "potted_flowering_azalea_bush": [29, 30, 18],
426 | "powder_snow": [248, 253, 253],
427 | "powered_rail": [93, 74, 50],
428 | "prismarine": [99, 156, 151],
429 | "prismarine_bricks": [99, 172, 158],
430 | "pumpkin": [198, 119, 24],
431 | "pumpkin_stem": [21, 21, 21],
432 | "purple_candle": [9, 3, 13],
433 | "purple_concrete": [100, 32, 156],
434 | "purple_concrete_powder": [132, 56, 178],
435 | "purple_glazed_terracotta": [110, 48, 152],
436 | "purple_shulker_box": [103, 32, 156],
437 | "purple_stained_glass": [127, 62, 177],
438 | "purple_stained_glass_pane": [15, 8, 21],
439 | "purple_terracotta": [118, 70, 86],
440 | "purple_wool": [122, 42, 173],
441 | "purpur_block": [170, 126, 170],
442 | "purpur_pillar": [172, 130, 172],
443 | "quartz_block": [236, 230, 223],
444 | "quartz_bricks": [235, 229, 222],
445 | "quartz_pillar": [236, 231, 224],
446 | "rail": [71, 63, 50],
447 | "raw_copper_block": [154, 106, 79],
448 | "raw_gold_block": [222, 169, 47],
449 | "raw_iron_block": [166, 136, 107],
450 | "red_candle": [13, 3, 3],
451 | "red_concrete": [142, 33, 33],
452 | "red_concrete_powder": [168, 54, 51],
453 | "red_glazed_terracotta": [182, 60, 53],
454 | "red_mushroom": [27, 9, 8],
455 | "red_mushroom_block": [200, 47, 45],
456 | "red_nether_bricks": [70, 7, 9],
457 | "red_sand": [191, 103, 33],
458 | "red_sandstone": [187, 99, 29],
459 | "red_shulker_box": [140, 31, 30],
460 | "red_stained_glass": [152, 50, 50],
461 | "red_stained_glass_pane": [18, 6, 6],
462 | "red_terracotta": [143, 61, 47],
463 | "red_tulip": [17, 25, 6],
464 | "red_wool": [161, 39, 35],
465 | "redstone_block": [176, 25, 5],
466 | "redstone_lamp": [95, 55, 30],
467 | "redstone_ore": [140, 110, 110],
468 | "redstone_torch": [21, 7, 5],
469 | "reinforced_deepslate": [80, 83, 79],
470 | "repeater": [160, 157, 156],
471 | "respawn_anchor": [76, 24, 150],
472 | "rooted_dirt": [144, 104, 77],
473 | "rose_bush": [48, 24, 14],
474 | "sand": [219, 207, 163],
475 | "sandstone": [216, 203, 156],
476 | "scaffolding": [154, 119, 66],
477 | "sculk": [13, 30, 36],
478 | "sculk_catalyst": [15, 32, 38],
479 | "sculk_sensor": [7, 70, 84],
480 | "sculk_shrieker": [40, 42, 34],
481 | "sculk_vein": [5, 34, 40],
482 | "sea_lantern": [172, 200, 190],
483 | "sea_pickle": [76, 82, 33],
484 | "seagrass": [15, 38, 2],
485 | "short_grass": [80, 80, 80],
486 | "shroomlight": [241, 147, 71],
487 | "shulker_box": [139, 97, 139],
488 | "slime_block": [112, 192, 92],
489 | "small_amethyst_bud": [16, 12, 24],
490 | "small_dripleaf": [24, 30, 11],
491 | "smithing_table": [57, 59, 71],
492 | "smoker": [85, 84, 81],
493 | "smooth_basalt": [73, 72, 78],
494 | "smooth_stone": [159, 159, 159],
495 | "snow": [249, 254, 254],
496 | "soul_lantern": [30, 41, 48],
497 | "soul_sand": [81, 62, 51],
498 | "soul_soil": [76, 58, 47],
499 | "soul_torch": [9, 9, 7],
500 | "spawner": [25, 32, 43],
501 | "sponge": [196, 192, 75],
502 | "spore_blossom": [100, 47, 77],
503 | "spruce_door": [106, 80, 49],
504 | "spruce_leaves": [79, 79, 79],
505 | "spruce_log": [59, 38, 17],
506 | "spruce_planks": [115, 85, 49],
507 | "spruce_sapling": [15, 20, 12],
508 | "spruce_trapdoor": [104, 79, 48],
509 | "stone": [126, 126, 126],
510 | "stone_bricks": [122, 122, 122],
511 | "stonecutter": [123, 119, 111],
512 | "stripped_acacia_log": [175, 93, 60],
513 | "stripped_bamboo_block": [193, 173, 80],
514 | "stripped_birch_log": [197, 176, 118],
515 | "stripped_cherry_log": [215, 145, 149],
516 | "stripped_crimson_stem": [137, 57, 90],
517 | "stripped_dark_oak_log": [73, 57, 36],
518 | "stripped_jungle_log": [171, 133, 85],
519 | "stripped_mangrove_log": [120, 54, 48],
520 | "stripped_oak_log": [177, 144, 86],
521 | "stripped_pale_oak_log": [246, 238, 237],
522 | "stripped_spruce_log": [116, 90, 52],
523 | "stripped_warped_stem": [58, 151, 148],
524 | "structure_block": [89, 74, 90],
525 | "sugar_cane": [82, 106, 56],
526 | "sunflower": [6, 16, 3],
527 | "tall_grass": [53, 53, 53],
528 | "tall_seagrass": [18, 41, 4],
529 | "target": [226, 170, 158],
530 | "terracotta": [152, 94, 68],
531 | "tinted_glass": [44, 39, 45],
532 | "tnt": [143, 62, 54],
533 | "torch": [11, 9, 5],
534 | "torchflower": [38, 38, 29],
535 | "tripwire": [21, 21, 21],
536 | "tripwire_hook": [26, 24, 21],
537 | "tube_coral": [28, 50, 118],
538 | "tube_coral_block": [49, 87, 207],
539 | "tube_coral_fan": [16, 28, 64],
540 | "tuff": [108, 109, 103],
541 | "tuff_bricks": [98, 103, 95],
542 | "turtle_egg": [118, 118, 100],
543 | "twisting_vines": [6, 40, 34],
544 | "twisting_vines_plant": [7, 48, 43],
545 | "vault": [55, 70, 79],
546 | "verdant_froglight": [229, 244, 228],
547 | "vine": [66, 66, 66],
548 | "warped_door": [45, 127, 121],
549 | "warped_fungus": [14, 21, 17],
550 | "warped_nylium": [43, 114, 101],
551 | "warped_planks": [43, 105, 99],
552 | "warped_roots": [7, 50, 45],
553 | "warped_stem": [58, 59, 78],
554 | "warped_trapdoor": [37, 94, 88],
555 | "warped_wart_block": [23, 120, 121],
556 | "weathered_chiseled_copper": [105, 151, 111],
557 | "weathered_copper": [108, 153, 110],
558 | "weathered_copper_bulb": [92, 127, 99],
559 | "weathered_copper_door": [89, 122, 89],
560 | "weathered_copper_grate": [74, 107, 78],
561 | "weathered_copper_trapdoor": [87, 122, 88],
562 | "weathered_cut_copper": [109, 145, 108],
563 | "weeping_vines": [11, 0, 0],
564 | "weeping_vines_plant": [60, 7, 5],
565 | "wet_sponge": [171, 181, 70],
566 | "white_candle": [17, 18, 18],
567 | "white_concrete": [207, 213, 214],
568 | "white_concrete_powder": [226, 227, 228],
569 | "white_glazed_terracotta": [189, 212, 203],
570 | "white_shulker_box": [216, 221, 221],
571 | "white_stained_glass": [255, 255, 255],
572 | "white_stained_glass_pane": [31, 31, 31],
573 | "white_terracotta": [210, 178, 161],
574 | "white_tulip": [16, 29, 12],
575 | "white_wool": [234, 236, 237],
576 | "wither_rose": [6, 6, 3],
577 | "yellow_candle": [17, 14, 4],
578 | "yellow_concrete": [241, 175, 21],
579 | "yellow_concrete_powder": [233, 199, 55],
580 | "yellow_glazed_terracotta": [234, 192, 89],
581 | "yellow_shulker_box": [248, 189, 29],
582 | "yellow_stained_glass": [230, 230, 50],
583 | "yellow_stained_glass_pane": [28, 28, 6],
584 | "yellow_terracotta": [186, 133, 35],
585 | "yellow_wool": [249, 198, 40]
586 | }
--------------------------------------------------------------------------------
/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: text-display-experiments
2 | version: '${project.version}'
3 | main: com.heledron.text_display_experiments.TextDisplayExperimentsPlugin
4 | api-version: '1.20'
5 |
6 | commands:
7 | items:
8 | description: Open the items menu
9 | permission: text-display-experiments.items
10 |
11 | permissions:
12 | text-display-experiments.items:
13 | description: Allows access to the items command
14 | default: op
--------------------------------------------------------------------------------