Skip to content

Plugin Docs: Texture Plugins

soir20 edited this page Jun 18, 2023 · 1 revision

MoreMcmeta texture plugins implement texturing effects.

Section Name

Every texture plugin is applied to a particular texture if its metadata contains the plugin's section name. The section name is returned by the sectionName() method of MoreMcmetaTexturePlugin.

For example, the section name of the MoreMcmeta default animation plugin is "animation". If an "animation" sub view is present in a texture's MetadataView, then the animation plugin will be applied to that texture.

Override Default Texture Plugin

If two plugins have the same section name, and one plugin is a default plugin, then the default plugin will be disabled. If two plugins have the same section name, but neither is a default plugin, then there will be a conflict.

Metadata Analyzer

The MetadataAnalyzer takes a MetadataView that contains all metadata for a given section. You do not need to retrieve the sub view assigned to your plugin's section name; that sub view is already provided to you.

MetadataView allows for values of all the Java primitive types, as well as InputStreams for binary data. If a value cannot be coerced into the requested typed, or a value does not exist at the given key or index, then the view returns Optional.empty(). Numerical conversions are lossless; MetadataView will not do lossy conversions. MetadataView does not differentiate between key-value objects and arrays: they are both treated as a "sub view." If you expect an array-like structure, you can retrieve values by index rather than by key.

The analyzer returns AnalyzedMetadata, which contains texture plugin-specific information read from the metadata. Plugins should implement this interface so that the AnalyzedMetadata holds whatever information the plugin needs. The exact same object will be provided to build() of the same plugin's ComponentBuilder.

Throwing Exceptions

The analyze() method may throw an InvalidMetadataException if the metadata is not valid. MoreMcmeta will handle these exceptions and log warnings with the exception message.

Keep the average user in mind: not everyone configuring textures will be a technical expert. Try to explain the problem simply and be specific as to where the issue is. Don't throw an exception with a message that amounts to "error."

MoreMcmeta will not handle exceptions besides InvalidMetadataException, and other exceptions may crash the game. If you use code that throws other exception types, you must catch and rethrow them as InvalidMetadataExceptions.

Frame Size and Blur/Clamp Conflicts

The AnalyzedMetadata interface stipulates four default methods: frameWidth(), frameHeight(), blur(), and clamp(). For each of these methods, the analyzer can return metadata with a value or return Optional.empty() to signal "no opinion." If two plugins provide a value, and those values are not equal, then there is a conflict and the metadata is considered invalid. There is no conflict when multiple plugins provide equal values or when no plugins provide a value. If no value is provided, MoreMcmeta will use a default.

Concurrency

Methods in the MetadataAnalyzer may be called from multiple threads. If any internal state is required, it must be thread-safe.

Component Builder

The ComponentBuilder takes AnalyzedMetadata and produces a TextureComponent, which responds to texture events. In addition, the builder may write colors to any frame of the texture being configured. Before discussing the build process in more detail, background information about plugin layering is necessary.

Plugin Layering

MoreMcmeta gives each plugin its own "layer:" a two-dimensional grid of pixels. The original image is considered the bottommost layer, with plugins making up the layers above.

The plugins applied first, those whose sections are first in a texture's MetadataView, have lower layers. Each plugin can only write to its own layer. The plugin can read colors from the layer below, but it has no knowledge of any layers above. If nothing has been written to the layer below at a particular pixel, then the plugin reads the color from the uppermost layer below its own layer that wrote to that pixel. However, the plugin does not know exactly which layer the color came from.

No matter the order of reads and writes, a plugin using a lower layer will not see any of the changes from a plugin using an upper layer. For example, say there are four layers, from bottom to top: the original image, plugin L, and plugin M, and plugin U. The original color of pixel (1, 2) is orange. Plugin M writes a magenta color to pixel (1, 2). If plugin L then reads (1, 2), it will see orange, not magenta. Plugin L is completely oblivious to writes by plugin M. When plugin M reads (1, 2), it will also see orange. However, if plugin U reads (1, 2), it see magenta.

By default, MoreMcmeta does not blend partially-transparent colors with colors from lower layers. However, you can use the Color.alphaBlend() method to blend a partially-transparent color with that in the layer below. MoreMcmeta does not do alpha blending automatically because it would make plugins that erase pixels impossible to create.

While the layering mechanism is useful to create overlays, it has limitations. MoreMcmeta does not render layers as additional quads. This means that all layers are rendered with the same brightness; you cannot make one layer emissive. ("Emissive textures" are actually full-brightness quads.) Furthermore, per-layer translations are impossible. For example, Minecraft's enchantment effect translates the texture to create an "animation." Currently, this effect cannot be replicated with MoreMcmeta's layering.

Building

In build(), the ComponentBuilder converts AnalyzedMetadata to a TextureComponent. Furthermore, the builder can write to the plugin's layer and read from layers below. This is useful to create static overlays that do not change regardless of what changes in the layers below, or change the texture's initial appearance.

Concurrency

Methods in the ComponentBuilder may be called from multiple threads. If any internal state is required, it must be thread-safe.

Texture Components

The TextureComponent can write colors to the texture onTick() (every 1/20th of a second) and onClose() (when the texture is being destroyed on game close or resource reloading). The onClose() is more useful to release resources needed by the component, if any.

Before the first tick, the texture is initialized with the first frame and its layers.

TextureComponent methods do not provide access to colors in the plugin's layer; you can only access colors in the layer below. If you need the current layer's colors, you should keep track of them inside the TextureComponent. (Most plugins simply keep a state variable and do not need access to all the current colors, so only direct access to the layer below is provided.)

Clone this wiki locally