├── .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 --------------------------------------------------------------------------------