mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-11 22:45:38 -04:00
1.15 Rendering Cleanup (#376)
* Make whitespace more consistent with Fabric norms * Remove implementation of disabled multi-sprite quad feature * Simplify quad header encoding - most attributes are now directly encoded to array data * Simplify material encoding - exploiting low number of possible materials * Add minimal validations related to sprite index * Codify magic numbers and make derivations more clear/explicit * Fix defect #374 * Bump patch versions * Use unmodified vanilla AO calculator
This commit is contained in:
parent
96946203be
commit
a5d709c3e1
70 changed files with 4426 additions and 4578 deletions
build.gradle
fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric
api/renderer/v1
impl/renderer
mixin/renderer/client
fabric-renderer-indigo
build.gradle
src/main
java/net/fabricmc/indigo
Indigo.javaIndigoMixinConfigPlugin.java
renderer
IndigoRenderer.javaRenderMaterialImpl.java
accessor
AccessAmbientOcclusionCalculator.javaAccessBlockModelRenderer.javaAccessBufferBuilder.javaAccessChunkRenderer.javaAccessChunkRendererRegion.java
aocalc
AoCalculator.javaAoConfig.javaAoFace.javaAoFaceData.javaAoLuminanceFix.javaAoVertexClampFunction.javaVanillaAoCalc.javaVanillaAoHelper.java
helper
mesh
mixin
BufferBuilderOffsetAccessor.javaMixinAmbientOcclusionCalculator.javaMixinBlockModelRenderer.javaMixinBufferBuilder.javaMixinChunkRebuildTask.javaMixinChunkRenderData.javaMixinChunkRenderer.javaMixinChunkRendererRegion.javaMixinItemRenderer.java
render
resources
|
@ -12,7 +12,7 @@ plugins {
|
|||
def ENV = System.getenv()
|
||||
|
||||
class Globals {
|
||||
static def baseVersion = "0.3.3"
|
||||
static def baseVersion = "0.3.4"
|
||||
static def mcVersion = "19w38b"
|
||||
static def yarnVersion = "+build.4"
|
||||
}
|
||||
|
|
|
@ -27,37 +27,37 @@ import net.minecraft.util.Identifier;
|
|||
* enhanced model rendering interfaces specified by the Fabric API.<p>
|
||||
*/
|
||||
public interface Renderer {
|
||||
/**
|
||||
* Obtain a new {@link MeshBuilder} instance used to create
|
||||
* baked models with enhanced features.<p>
|
||||
*
|
||||
* Renderer does not retain a reference to returned instances and they should be re-used for
|
||||
* multiple models when possible to avoid memory allocation overhead.
|
||||
*/
|
||||
MeshBuilder meshBuilder();
|
||||
|
||||
/**
|
||||
* Obtain a new {@link MaterialFinder} instance used to retrieve
|
||||
* standard {@link RenderMaterial} instances.<p>
|
||||
*
|
||||
* Renderer does not retain a reference to returned instances and they should be re-used for
|
||||
* multiple materials when possible to avoid memory allocation overhead.
|
||||
*/
|
||||
MaterialFinder materialFinder();
|
||||
/**
|
||||
* Obtain a new {@link MeshBuilder} instance used to create
|
||||
* baked models with enhanced features.<p>
|
||||
*
|
||||
* Renderer does not retain a reference to returned instances and they should be re-used for
|
||||
* multiple models when possible to avoid memory allocation overhead.
|
||||
*/
|
||||
MeshBuilder meshBuilder();
|
||||
|
||||
/**
|
||||
* Return a material previously registered via {@link #registerMaterial(Identifier, RenderMaterial)}.
|
||||
* Will return null if no material was found matching the given identifier.
|
||||
*/
|
||||
RenderMaterial materialById(Identifier id);
|
||||
|
||||
/**
|
||||
* Register a material for re-use by other mods or models within a mod.
|
||||
* The registry does not persist registrations - mods must create and register
|
||||
* all materials at game initialization.<p>
|
||||
*
|
||||
* Returns false if a material with the given identifier is already present,
|
||||
* leaving the existing material intact.
|
||||
*/
|
||||
boolean registerMaterial(Identifier id, RenderMaterial material);
|
||||
/**
|
||||
* Obtain a new {@link MaterialFinder} instance used to retrieve
|
||||
* standard {@link RenderMaterial} instances.<p>
|
||||
*
|
||||
* Renderer does not retain a reference to returned instances and they should be re-used for
|
||||
* multiple materials when possible to avoid memory allocation overhead.
|
||||
*/
|
||||
MaterialFinder materialFinder();
|
||||
|
||||
/**
|
||||
* Return a material previously registered via {@link #registerMaterial(Identifier, RenderMaterial)}.
|
||||
* Will return null if no material was found matching the given identifier.
|
||||
*/
|
||||
RenderMaterial materialById(Identifier id);
|
||||
|
||||
/**
|
||||
* Register a material for re-use by other mods or models within a mod.
|
||||
* The registry does not persist registrations - mods must create and register
|
||||
* all materials at game initialization.<p>
|
||||
*
|
||||
* Returns false if a material with the given identifier is already present,
|
||||
* leaving the existing material intact.
|
||||
*/
|
||||
boolean registerMaterial(Identifier id, RenderMaterial material);
|
||||
}
|
||||
|
|
|
@ -22,25 +22,25 @@ import net.fabricmc.fabric.impl.renderer.RendererAccessImpl;
|
|||
* Registration and access for rendering extensions.
|
||||
*/
|
||||
public interface RendererAccess {
|
||||
RendererAccess INSTANCE = RendererAccessImpl.INSTANCE;
|
||||
RendererAccess INSTANCE = RendererAccessImpl.INSTANCE;
|
||||
|
||||
/**
|
||||
* Rendering extension mods must implement {@link Renderer} and
|
||||
* call this method during initialization.<p>
|
||||
*
|
||||
* Only one {@link Renderer} plug-in can be active in any game instance.
|
||||
* If a second mod attempts to register this method will throw an UnsupportedOperationException.
|
||||
*/
|
||||
void registerRenderer(Renderer plugin);
|
||||
/**
|
||||
* Rendering extension mods must implement {@link Renderer} and
|
||||
* call this method during initialization.<p>
|
||||
*
|
||||
* Only one {@link Renderer} plug-in can be active in any game instance.
|
||||
* If a second mod attempts to register this method will throw an UnsupportedOperationException.
|
||||
*/
|
||||
void registerRenderer(Renderer plugin);
|
||||
|
||||
/**
|
||||
* Access to the current {@link Renderer} for creating and retrieving model builders
|
||||
* and materials. Will return null if no render plug in is active.
|
||||
*/
|
||||
Renderer getRenderer();
|
||||
/**
|
||||
* Access to the current {@link Renderer} for creating and retrieving model builders
|
||||
* and materials. Will return null if no render plug in is active.
|
||||
*/
|
||||
Renderer getRenderer();
|
||||
|
||||
/**
|
||||
* Performant test for {@link #getRenderer()} != null;
|
||||
*/
|
||||
boolean hasRenderer();
|
||||
/**
|
||||
* Performant test for {@link #getRenderer()} != null;
|
||||
*/
|
||||
boolean hasRenderer();
|
||||
}
|
||||
|
|
|
@ -28,80 +28,80 @@ import net.minecraft.block.BlockRenderLayer;
|
|||
* Must be obtained via {@link Renderer#materialFinder()}.
|
||||
*/
|
||||
public interface MaterialFinder {
|
||||
/**
|
||||
* Returns the standard material encoding all
|
||||
* of the current settings in this finder. The settings in
|
||||
* this finder are not changed.<p>
|
||||
*
|
||||
* Resulting instances can and should be re-used to prevent
|
||||
* needless memory allocation. {@link Renderer} implementations
|
||||
* may or may not cache standard material instances.
|
||||
*/
|
||||
RenderMaterial find();
|
||||
/**
|
||||
* Returns the standard material encoding all
|
||||
* of the current settings in this finder. The settings in
|
||||
* this finder are not changed.<p>
|
||||
*
|
||||
* Resulting instances can and should be re-used to prevent
|
||||
* needless memory allocation. {@link Renderer} implementations
|
||||
* may or may not cache standard material instances.
|
||||
*/
|
||||
RenderMaterial find();
|
||||
|
||||
/**
|
||||
* Resets this instance to default values. Values will match those
|
||||
* in effect when an instance is newly obtained via {@link Renderer#materialFinder()}.
|
||||
*/
|
||||
MaterialFinder clear();
|
||||
|
||||
/**
|
||||
*
|
||||
* Reserved for future use. Behavior for values > 1 is currently undefined.
|
||||
*/
|
||||
MaterialFinder spriteDepth(int depth);
|
||||
/**
|
||||
* Resets this instance to default values. Values will match those
|
||||
* in effect when an instance is newly obtained via {@link Renderer#materialFinder()}.
|
||||
*/
|
||||
MaterialFinder clear();
|
||||
|
||||
/**
|
||||
* Defines how sprite pixels will be blended with the scene.
|
||||
* Accepts {link @BlockRenderLayer} values and blending behavior
|
||||
* will emulate the way that Minecraft renders those instances. This does
|
||||
* NOT mean the sprite will be rendered in a specific render pass - some
|
||||
* implementations may not use the standard vanilla render passes.<p>
|
||||
*
|
||||
* CAN be null and is null by default. A null value means the renderer
|
||||
* will use the value normally associated with the block being rendered, or
|
||||
* {@code TRANSLUCENT} for item renders. (Normal Minecraft rendering)
|
||||
*
|
||||
* @deprecated Use {@code BlendMode} version instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default MaterialFinder blendMode(int spriteIndex, BlockRenderLayer renderLayer) {
|
||||
return blendMode(spriteIndex, BlendMode.fromRenderLayer(renderLayer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how sprite pixels will be blended with the scene.<p>
|
||||
*
|
||||
* See {@link BlendMode} for more information.
|
||||
*/
|
||||
MaterialFinder blendMode(int spriteIndex, BlendMode blendMode);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for quad color index unless disabled.<p>
|
||||
*/
|
||||
MaterialFinder disableColorIndex(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for diffuse shading unless disabled.
|
||||
*/
|
||||
MaterialFinder disableDiffuse(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for ambient occlusion unless disabled.
|
||||
*/
|
||||
MaterialFinder disableAo(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* When true, sprite texture and color will be rendered at full brightness.
|
||||
* Lightmap values provided via {@link QuadEmitter#lightmap(int)} will be ignored.
|
||||
* False by default<p>
|
||||
*
|
||||
* This is the preferred method for emissive lighting effects. Some renderers
|
||||
* with advanced lighting models may not use block lightmaps and this method will
|
||||
* allow per-sprite emissive lighting in future extensions that support overlay sprites.<p>
|
||||
*
|
||||
* Note that color will still be modified by diffuse shading and ambient occlusion,
|
||||
* unless disabled via {@link #disableAo(int, boolean)} and {@link #disableDiffuse(int, boolean)}.
|
||||
*/
|
||||
MaterialFinder emissive(int spriteIndex, boolean isEmissive);
|
||||
/**
|
||||
*
|
||||
* Reserved for future use. Behavior for values > 1 is currently undefined.
|
||||
*/
|
||||
MaterialFinder spriteDepth(int depth);
|
||||
|
||||
/**
|
||||
* Defines how sprite pixels will be blended with the scene.
|
||||
* Accepts {link @BlockRenderLayer} values and blending behavior
|
||||
* will emulate the way that Minecraft renders those instances. This does
|
||||
* NOT mean the sprite will be rendered in a specific render pass - some
|
||||
* implementations may not use the standard vanilla render passes.<p>
|
||||
*
|
||||
* CAN be null and is null by default. A null value means the renderer
|
||||
* will use the value normally associated with the block being rendered, or
|
||||
* {@code TRANSLUCENT} for item renders. (Normal Minecraft rendering)
|
||||
*
|
||||
* @deprecated Use {@code BlendMode} version instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default MaterialFinder blendMode(int spriteIndex, BlockRenderLayer renderLayer) {
|
||||
return blendMode(spriteIndex, BlendMode.fromRenderLayer(renderLayer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how sprite pixels will be blended with the scene.<p>
|
||||
*
|
||||
* See {@link BlendMode} for more information.
|
||||
*/
|
||||
MaterialFinder blendMode(int spriteIndex, BlendMode blendMode);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for quad color index unless disabled.<p>
|
||||
*/
|
||||
MaterialFinder disableColorIndex(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for diffuse shading unless disabled.
|
||||
*/
|
||||
MaterialFinder disableDiffuse(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* Vertex color(s) will be modified for ambient occlusion unless disabled.
|
||||
*/
|
||||
MaterialFinder disableAo(int spriteIndex, boolean disable);
|
||||
|
||||
/**
|
||||
* When true, sprite texture and color will be rendered at full brightness.
|
||||
* Lightmap values provided via {@link QuadEmitter#lightmap(int)} will be ignored.
|
||||
* False by default<p>
|
||||
*
|
||||
* This is the preferred method for emissive lighting effects. Some renderers
|
||||
* with advanced lighting models may not use block lightmaps and this method will
|
||||
* allow per-sprite emissive lighting in future extensions that support overlay sprites.<p>
|
||||
*
|
||||
* Note that color will still be modified by diffuse shading and ambient occlusion,
|
||||
* unless disabled via {@link #disableAo(int, boolean)} and {@link #disableDiffuse(int, boolean)}.
|
||||
*/
|
||||
MaterialFinder emissive(int spriteIndex, boolean isEmissive);
|
||||
}
|
||||
|
|
|
@ -62,21 +62,21 @@ import net.minecraft.util.Identifier;
|
|||
* no requirement that special materials be cross-compatible.
|
||||
*/
|
||||
public interface RenderMaterial {
|
||||
/**
|
||||
* This will be identical to the material that would be obtained by calling {@link MaterialFinder#find()}
|
||||
* on a new, unaltered, {@link MaterialFinder} instance. It is defined here for clarity and convenience.
|
||||
*
|
||||
* Quads using this material use {@link Block#getRenderLayer()} of the associated block to determine texture blending,
|
||||
* honor block color index, are non-emissive, and apply both diffuse and ambient occlusion shading to vertex colors.<p>
|
||||
*
|
||||
* All standard, non-fluid baked models are rendered using this material.
|
||||
*/
|
||||
Identifier MATERIAL_STANDARD = new Identifier("fabric", "standard");
|
||||
|
||||
/**
|
||||
* How many sprite color/uv coordinates are in the material.
|
||||
* Behavior for values > 1 is currently undefined.
|
||||
* See {@link MaterialFinder#spriteDepth(int)}
|
||||
*/
|
||||
int spriteDepth();
|
||||
/**
|
||||
* This will be identical to the material that would be obtained by calling {@link MaterialFinder#find()}
|
||||
* on a new, unaltered, {@link MaterialFinder} instance. It is defined here for clarity and convenience.
|
||||
*
|
||||
* Quads using this material use {@link Block#getRenderLayer()} of the associated block to determine texture blending,
|
||||
* honor block color index, are non-emissive, and apply both diffuse and ambient occlusion shading to vertex colors.<p>
|
||||
*
|
||||
* All standard, non-fluid baked models are rendered using this material.
|
||||
*/
|
||||
Identifier MATERIAL_STANDARD = new Identifier("fabric", "standard");
|
||||
|
||||
/**
|
||||
* How many sprite color/uv coordinates are in the material.
|
||||
* Behavior for values > 1 is currently undefined.
|
||||
* See {@link MaterialFinder#spriteDepth(int)}
|
||||
*/
|
||||
int spriteDepth();
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
|||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
public interface Mesh {
|
||||
/**
|
||||
* Use to access all of the quads encoded in this mesh. The quad instances
|
||||
* sent to the consumer will likely be threadlocal/reused and should never
|
||||
* be retained by the consumer.
|
||||
*/
|
||||
public void forEach(Consumer<QuadView> consumer);
|
||||
/**
|
||||
* Use to access all of the quads encoded in this mesh. The quad instances
|
||||
* sent to the consumer will likely be threadlocal/reused and should never
|
||||
* be retained by the consumer.
|
||||
*/
|
||||
public void forEach(Consumer<QuadView> consumer);
|
||||
}
|
||||
|
|
|
@ -27,16 +27,16 @@ import net.minecraft.client.render.BufferBuilder;
|
|||
* ModelRenderer to allow compatibility across diverse implementations.<p>
|
||||
*/
|
||||
public interface MeshBuilder {
|
||||
/**
|
||||
* Returns the {@link QuadEmitter} used to append quad to this mesh.
|
||||
* Calling this method a second time invalidates any prior result.
|
||||
* Do not retain references outside the context of building the mesh.
|
||||
*/
|
||||
QuadEmitter getEmitter();
|
||||
|
||||
/**
|
||||
* Returns a new {@link Mesh} instance containing all
|
||||
* quads added to this builder and resets the builder to an empty state<p>
|
||||
*/
|
||||
Mesh build();
|
||||
/**
|
||||
* Returns the {@link QuadEmitter} used to append quad to this mesh.
|
||||
* Calling this method a second time invalidates any prior result.
|
||||
* Do not retain references outside the context of building the mesh.
|
||||
*/
|
||||
QuadEmitter getEmitter();
|
||||
|
||||
/**
|
||||
* Returns a new {@link Mesh} instance containing all
|
||||
* quads added to this builder and resets the builder to an empty state<p>
|
||||
*/
|
||||
Mesh build();
|
||||
}
|
||||
|
|
|
@ -34,207 +34,207 @@ import net.minecraft.util.math.Direction;
|
|||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
public interface MutableQuadView extends QuadView {
|
||||
/**
|
||||
* Causes texture to appear with no rotation.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 90 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_90 = 1;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 180 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_180 = 2;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 270 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_270 = 3;
|
||||
|
||||
/**
|
||||
* When enabled, texture coordinate are assigned based on vertex position.
|
||||
* Any existing uv coordinates will be replaced.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.<p>
|
||||
*
|
||||
* UV lock always derives texture coordinates based on nominal face, even
|
||||
* when the quad is not co-planar with that face, and the result is
|
||||
* the same as if the quad were projected onto the nominal face, which
|
||||
* is usually the desired result.<p>
|
||||
*/
|
||||
int BAKE_LOCK_UV = 4;
|
||||
|
||||
/**
|
||||
* When set, U texture coordinates for the given sprite are
|
||||
* flipped as part of baking. Can be useful for some randomization
|
||||
* and texture mapping scenarios. Results are different than what
|
||||
* can be obtained via rotation and both can be applied.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_FLIP_U = 8;
|
||||
|
||||
/**
|
||||
* Same as {@link MutableQuadView#BAKE_FLIP_U} but for V coordinate.
|
||||
*/
|
||||
int BAKE_FLIP_V = 16;
|
||||
|
||||
/**
|
||||
* UV coordinates by default are assumed to be 0-16 scale for consistency
|
||||
* with conventional Minecraft model format. This is scaled to 0-1 during
|
||||
* baking before interpolation. Model loaders that already have 0-1 coordinates
|
||||
* can avoid wasteful multiplication/division by passing 0-1 coordinates directly.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_NORMALIZED = 32;
|
||||
|
||||
/**
|
||||
* Assigns a different material to this quad. Useful for transformation of
|
||||
* existing meshes because lighting and texture blending are controlled by material.<p>
|
||||
*/
|
||||
MutableQuadView material(RenderMaterial material);
|
||||
|
||||
/**
|
||||
* If non-null, quad is coplanar with a block face which, if known, simplifies
|
||||
* or shortcuts geometric analysis that might otherwise be needed.
|
||||
* Set to null if quad is not coplanar or if this is not known.
|
||||
* Also controls face culling during block rendering.<p>
|
||||
*
|
||||
* Null by default.<p>
|
||||
*
|
||||
* When called with a non-null value, also sets {@link #nominalFace(Direction)}
|
||||
* to the same value.<p>
|
||||
*
|
||||
* This is different than the value reported by {@link BakedQuad#getFace()}. That value
|
||||
* is computed based on face geometry and must be non-null in vanilla quads.
|
||||
* That computed value is returned by {@link #lightFace()}.
|
||||
*/
|
||||
MutableQuadView cullFace(Direction face);
|
||||
|
||||
/**
|
||||
* Provides a hint to renderer about the facing of this quad. Not required,
|
||||
* but if provided can shortcut some geometric analysis if the quad is parallel to a block face.
|
||||
* Should be the expected value of {@link #lightFace()}. Value will be confirmed
|
||||
* and if invalid the correct light face will be calculated.<p>
|
||||
*
|
||||
* Null by default, and set automatically by {@link #cullFace()}.<p>
|
||||
*
|
||||
* Models may also find this useful as the face for texture UV locking and rotation semantics.<p>
|
||||
*
|
||||
* NOTE: This value is not persisted independently when the quad is encoded.
|
||||
* When reading encoded quads, this value will always be the same as {@link #lightFace()}.
|
||||
*/
|
||||
MutableQuadView nominalFace(Direction face);
|
||||
|
||||
/**
|
||||
* Value functions identically to {@link BakedQuad#getColorIndex()} and is
|
||||
* used by renderer / model builder in same way. Default value is -1.
|
||||
*/
|
||||
MutableQuadView colorIndex(int colorIndex);
|
||||
|
||||
/**
|
||||
* Enables bulk vertex data transfer using the standard Minecraft vertex formats.
|
||||
* This method should be performant whenever caller's vertex representation makes it feasible.<p>
|
||||
*
|
||||
* Calling this method does not emit the quad.
|
||||
*/
|
||||
MutableQuadView fromVanilla(int[] quadData, int startIndex, boolean isItem);
|
||||
|
||||
/**
|
||||
* Encodes an integer tag with this quad that can later be retrieved via
|
||||
* {@link QuadView#tag()}. Useful for models that want to perform conditional
|
||||
* transformation or filtering on static meshes.
|
||||
*/
|
||||
MutableQuadView tag(int tag);
|
||||
|
||||
/**
|
||||
* Sets the geometric vertex position for the given vertex,
|
||||
* relative to block origin. (0,0,0). Minecraft rendering is designed
|
||||
* for models that fit within a single block space and is recommended
|
||||
* that coordinates remain in the 0-1 range, with multi-block meshes
|
||||
* split into multiple per-block models.
|
||||
*/
|
||||
MutableQuadView pos(int vertexIndex, float x, float y, float z);
|
||||
|
||||
/**
|
||||
* Same as {@link #pos(int, float, float, float)} but accepts vector type.
|
||||
*/
|
||||
default MutableQuadView pos(int vertexIndex, Vector3f vec) {
|
||||
return pos(vertexIndex, vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a vertex normal. Models that have per-vertex
|
||||
* normals should include them to get correct lighting when it matters.
|
||||
* Computed face normal is used when no vertex normal is provided.<p>
|
||||
*
|
||||
* {@link Renderer} implementations should honor vertex normals for
|
||||
* diffuse lighting - modifying vertex color(s) or packing normals in the vertex
|
||||
* buffer as appropriate for the rendering method/vertex format in effect.
|
||||
*/
|
||||
MutableQuadView normal(int vertexIndex, float x, float y, float z);
|
||||
|
||||
/**
|
||||
* Same as {@link #normal(int, float, float, float)} but accepts vector type.
|
||||
*/
|
||||
default MutableQuadView normal(int vertexIndex, Vector3f vec) {
|
||||
return normal(vertexIndex, vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept vanilla lightmap values. Input values will override lightmap values
|
||||
* computed from world state if input values are higher. Exposed for completeness
|
||||
* but some rendering implementations with non-standard lighting model may not honor it. <p>
|
||||
*
|
||||
* For emissive rendering, it is better to use {@link MaterialFinder#emissive(int, boolean)}.
|
||||
*/
|
||||
MutableQuadView lightmap(int vertexIndex, int lightmap);
|
||||
|
||||
/**
|
||||
* Convenience: set lightmap for all vertices at once. <p>
|
||||
*
|
||||
* For emissive rendering, it is better to use {@link MaterialFinder#emissive(int, boolean)}.
|
||||
* See {@link #lightmap(int, int)}.
|
||||
*/
|
||||
default MutableQuadView lightmap(int b0, int b1, int b2, int b3) {
|
||||
lightmap(0, b0);
|
||||
lightmap(1, b1);
|
||||
lightmap(2, b2);
|
||||
lightmap(3, b3);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sprite color. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView spriteColor(int vertexIndex, int spriteIndex, int color);
|
||||
|
||||
/**
|
||||
* Convenience: set sprite color for all vertices at once. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
default MutableQuadView spriteColor(int spriteIndex, int c0, int c1, int c2, int c3) {
|
||||
spriteColor(0, spriteIndex, c0);
|
||||
spriteColor(1, spriteIndex, c1);
|
||||
spriteColor(2, spriteIndex, c2);
|
||||
spriteColor(3, spriteIndex, c3);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sprite atlas coordinates. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView sprite(int vertexIndex, int spriteIndex, float u, float v);
|
||||
|
||||
/**
|
||||
* Assigns sprite atlas u,v coordinates to this quad for the given sprite.
|
||||
* Can handle UV locking, rotation, interpolation, etc. Control this behavior
|
||||
* by passing additive combinations of the BAKE_ flags defined in this interface.
|
||||
* Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
|
||||
/**
|
||||
* Causes texture to appear with no rotation.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 90 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_90 = 1;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 180 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_180 = 2;
|
||||
|
||||
/**
|
||||
* Causes texture to appear rotated 270 deg. relative to nominal face.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_ROTATE_270 = 3;
|
||||
|
||||
/**
|
||||
* When enabled, texture coordinate are assigned based on vertex position.
|
||||
* Any existing uv coordinates will be replaced.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.<p>
|
||||
*
|
||||
* UV lock always derives texture coordinates based on nominal face, even
|
||||
* when the quad is not co-planar with that face, and the result is
|
||||
* the same as if the quad were projected onto the nominal face, which
|
||||
* is usually the desired result.<p>
|
||||
*/
|
||||
int BAKE_LOCK_UV = 4;
|
||||
|
||||
/**
|
||||
* When set, U texture coordinates for the given sprite are
|
||||
* flipped as part of baking. Can be useful for some randomization
|
||||
* and texture mapping scenarios. Results are different than what
|
||||
* can be obtained via rotation and both can be applied.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_FLIP_U = 8;
|
||||
|
||||
/**
|
||||
* Same as {@link MutableQuadView#BAKE_FLIP_U} but for V coordinate.
|
||||
*/
|
||||
int BAKE_FLIP_V = 16;
|
||||
|
||||
/**
|
||||
* UV coordinates by default are assumed to be 0-16 scale for consistency
|
||||
* with conventional Minecraft model format. This is scaled to 0-1 during
|
||||
* baking before interpolation. Model loaders that already have 0-1 coordinates
|
||||
* can avoid wasteful multiplication/division by passing 0-1 coordinates directly.
|
||||
* Pass in bakeFlags parameter to {@link #spriteBake(int, Sprite, int)}.
|
||||
*/
|
||||
int BAKE_NORMALIZED = 32;
|
||||
|
||||
/**
|
||||
* Assigns a different material to this quad. Useful for transformation of
|
||||
* existing meshes because lighting and texture blending are controlled by material.<p>
|
||||
*/
|
||||
MutableQuadView material(RenderMaterial material);
|
||||
|
||||
/**
|
||||
* If non-null, quad is coplanar with a block face which, if known, simplifies
|
||||
* or shortcuts geometric analysis that might otherwise be needed.
|
||||
* Set to null if quad is not coplanar or if this is not known.
|
||||
* Also controls face culling during block rendering.<p>
|
||||
*
|
||||
* Null by default.<p>
|
||||
*
|
||||
* When called with a non-null value, also sets {@link #nominalFace(Direction)}
|
||||
* to the same value.<p>
|
||||
*
|
||||
* This is different than the value reported by {@link BakedQuad#getFace()}. That value
|
||||
* is computed based on face geometry and must be non-null in vanilla quads.
|
||||
* That computed value is returned by {@link #lightFace()}.
|
||||
*/
|
||||
MutableQuadView cullFace(Direction face);
|
||||
|
||||
/**
|
||||
* Provides a hint to renderer about the facing of this quad. Not required,
|
||||
* but if provided can shortcut some geometric analysis if the quad is parallel to a block face.
|
||||
* Should be the expected value of {@link #lightFace()}. Value will be confirmed
|
||||
* and if invalid the correct light face will be calculated.<p>
|
||||
*
|
||||
* Null by default, and set automatically by {@link #cullFace()}.<p>
|
||||
*
|
||||
* Models may also find this useful as the face for texture UV locking and rotation semantics.<p>
|
||||
*
|
||||
* NOTE: This value is not persisted independently when the quad is encoded.
|
||||
* When reading encoded quads, this value will always be the same as {@link #lightFace()}.
|
||||
*/
|
||||
MutableQuadView nominalFace(Direction face);
|
||||
|
||||
/**
|
||||
* Value functions identically to {@link BakedQuad#getColorIndex()} and is
|
||||
* used by renderer / model builder in same way. Default value is -1.
|
||||
*/
|
||||
MutableQuadView colorIndex(int colorIndex);
|
||||
|
||||
/**
|
||||
* Enables bulk vertex data transfer using the standard Minecraft vertex formats.
|
||||
* This method should be performant whenever caller's vertex representation makes it feasible.<p>
|
||||
*
|
||||
* Calling this method does not emit the quad.
|
||||
*/
|
||||
MutableQuadView fromVanilla(int[] quadData, int startIndex, boolean isItem);
|
||||
|
||||
/**
|
||||
* Encodes an integer tag with this quad that can later be retrieved via
|
||||
* {@link QuadView#tag()}. Useful for models that want to perform conditional
|
||||
* transformation or filtering on static meshes.
|
||||
*/
|
||||
MutableQuadView tag(int tag);
|
||||
|
||||
/**
|
||||
* Sets the geometric vertex position for the given vertex,
|
||||
* relative to block origin. (0,0,0). Minecraft rendering is designed
|
||||
* for models that fit within a single block space and is recommended
|
||||
* that coordinates remain in the 0-1 range, with multi-block meshes
|
||||
* split into multiple per-block models.
|
||||
*/
|
||||
MutableQuadView pos(int vertexIndex, float x, float y, float z);
|
||||
|
||||
/**
|
||||
* Same as {@link #pos(int, float, float, float)} but accepts vector type.
|
||||
*/
|
||||
default MutableQuadView pos(int vertexIndex, Vector3f vec) {
|
||||
return pos(vertexIndex, vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a vertex normal. Models that have per-vertex
|
||||
* normals should include them to get correct lighting when it matters.
|
||||
* Computed face normal is used when no vertex normal is provided.<p>
|
||||
*
|
||||
* {@link Renderer} implementations should honor vertex normals for
|
||||
* diffuse lighting - modifying vertex color(s) or packing normals in the vertex
|
||||
* buffer as appropriate for the rendering method/vertex format in effect.
|
||||
*/
|
||||
MutableQuadView normal(int vertexIndex, float x, float y, float z);
|
||||
|
||||
/**
|
||||
* Same as {@link #normal(int, float, float, float)} but accepts vector type.
|
||||
*/
|
||||
default MutableQuadView normal(int vertexIndex, Vector3f vec) {
|
||||
return normal(vertexIndex, vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept vanilla lightmap values. Input values will override lightmap values
|
||||
* computed from world state if input values are higher. Exposed for completeness
|
||||
* but some rendering implementations with non-standard lighting model may not honor it. <p>
|
||||
*
|
||||
* For emissive rendering, it is better to use {@link MaterialFinder#emissive(int, boolean)}.
|
||||
*/
|
||||
MutableQuadView lightmap(int vertexIndex, int lightmap);
|
||||
|
||||
/**
|
||||
* Convenience: set lightmap for all vertices at once. <p>
|
||||
*
|
||||
* For emissive rendering, it is better to use {@link MaterialFinder#emissive(int, boolean)}.
|
||||
* See {@link #lightmap(int, int)}.
|
||||
*/
|
||||
default MutableQuadView lightmap(int b0, int b1, int b2, int b3) {
|
||||
lightmap(0, b0);
|
||||
lightmap(1, b1);
|
||||
lightmap(2, b2);
|
||||
lightmap(3, b3);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sprite color. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView spriteColor(int vertexIndex, int spriteIndex, int color);
|
||||
|
||||
/**
|
||||
* Convenience: set sprite color for all vertices at once. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
default MutableQuadView spriteColor(int spriteIndex, int c0, int c1, int c2, int c3) {
|
||||
spriteColor(0, spriteIndex, c0);
|
||||
spriteColor(1, spriteIndex, c1);
|
||||
spriteColor(2, spriteIndex, c2);
|
||||
spriteColor(3, spriteIndex, c3);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sprite atlas coordinates. Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView sprite(int vertexIndex, int spriteIndex, float u, float v);
|
||||
|
||||
/**
|
||||
* Assigns sprite atlas u,v coordinates to this quad for the given sprite.
|
||||
* Can handle UV locking, rotation, interpolation, etc. Control this behavior
|
||||
* by passing additive combinations of the BAKE_ flags defined in this interface.
|
||||
* Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*/
|
||||
MutableQuadView spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
|
||||
}
|
||||
|
|
|
@ -35,141 +35,140 @@ import net.minecraft.util.math.Direction;
|
|||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
public interface QuadEmitter extends MutableQuadView {
|
||||
@Override
|
||||
QuadEmitter material(RenderMaterial material);
|
||||
@Override
|
||||
QuadEmitter material(RenderMaterial material);
|
||||
|
||||
@Override
|
||||
QuadEmitter cullFace(Direction face);
|
||||
@Override
|
||||
QuadEmitter cullFace(Direction face);
|
||||
|
||||
@Override
|
||||
QuadEmitter nominalFace(Direction face);
|
||||
@Override
|
||||
QuadEmitter nominalFace(Direction face);
|
||||
|
||||
@Override
|
||||
QuadEmitter colorIndex(int colorIndex);
|
||||
@Override
|
||||
QuadEmitter colorIndex(int colorIndex);
|
||||
|
||||
@Override
|
||||
QuadEmitter fromVanilla(int[] quadData, int startIndex, boolean isItem);
|
||||
@Override
|
||||
QuadEmitter fromVanilla(int[] quadData, int startIndex, boolean isItem);
|
||||
|
||||
@Override
|
||||
QuadEmitter tag(int tag);
|
||||
@Override
|
||||
QuadEmitter tag(int tag);
|
||||
|
||||
@Override
|
||||
QuadEmitter pos(int vertexIndex, float x, float y, float z);
|
||||
@Override
|
||||
QuadEmitter pos(int vertexIndex, float x, float y, float z);
|
||||
|
||||
@Override
|
||||
default QuadEmitter pos(int vertexIndex, Vector3f vec) {
|
||||
MutableQuadView.super.pos(vertexIndex, vec);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
default QuadEmitter pos(int vertexIndex, Vector3f vec) {
|
||||
MutableQuadView.super.pos(vertexIndex, vec);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default QuadEmitter normal(int vertexIndex, Vector3f vec) {
|
||||
MutableQuadView.super.normal(vertexIndex, vec);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
default QuadEmitter normal(int vertexIndex, Vector3f vec) {
|
||||
MutableQuadView.super.normal(vertexIndex, vec);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
QuadEmitter lightmap(int vertexIndex, int lightmap);
|
||||
@Override
|
||||
QuadEmitter lightmap(int vertexIndex, int lightmap);
|
||||
|
||||
@Override
|
||||
default QuadEmitter lightmap(int b0, int b1, int b2, int b3) {
|
||||
MutableQuadView.super.lightmap(b0, b1, b2, b3);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
default QuadEmitter lightmap(int b0, int b1, int b2, int b3) {
|
||||
MutableQuadView.super.lightmap(b0, b1, b2, b3);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
QuadEmitter spriteColor(int vertexIndex, int spriteIndex, int color);
|
||||
@Override
|
||||
QuadEmitter spriteColor(int vertexIndex, int spriteIndex, int color);
|
||||
|
||||
@Override
|
||||
default QuadEmitter spriteColor(int spriteIndex, int c0, int c1, int c2, int c3) {
|
||||
MutableQuadView.super.spriteColor(spriteIndex, c0, c1, c2, c3);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
default QuadEmitter spriteColor(int spriteIndex, int c0, int c1, int c2, int c3) {
|
||||
MutableQuadView.super.spriteColor(spriteIndex, c0, c1, c2, c3);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
QuadEmitter sprite(int vertexIndex, int spriteIndex, float u, float v);
|
||||
@Override
|
||||
QuadEmitter sprite(int vertexIndex, int spriteIndex, float u, float v);
|
||||
|
||||
default QuadEmitter spriteUnitSquare(int spriteIndex) {
|
||||
sprite(0, spriteIndex, 0, 0);
|
||||
sprite(1, spriteIndex, 0, 1);
|
||||
sprite(2, spriteIndex, 1, 1);
|
||||
sprite(3, spriteIndex, 1, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
QuadEmitter spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
|
||||
|
||||
/**
|
||||
* Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)}
|
||||
* is effectively zero - meaning the face is a cull face.
|
||||
*/
|
||||
final float CULL_FACE_EPSILON = 0.00001f;
|
||||
|
||||
/**
|
||||
* Helper method to assign vertex coordinates for a square aligned with the given face.
|
||||
* Ensures that vertex order is consistent with vanilla convention. (Incorrect order can
|
||||
* lead to bad AO lighting unless enhanced lighting logic is available/enabled.)<p>
|
||||
*
|
||||
* Square will be parallel to the given face and coplanar with the face (and culled if the
|
||||
* face is occluded) if the depth parameter is approximately zero. See {@link #CULL_FACE_EPSILON}.<p>
|
||||
*
|
||||
* All coordinates should be normalized (0-1).
|
||||
*/
|
||||
default QuadEmitter square(Direction nominalFace, float left, float bottom, float right, float top, float depth) {
|
||||
if(Math.abs(depth) < CULL_FACE_EPSILON) {
|
||||
cullFace(nominalFace);
|
||||
depth = 0; // avoid any inconsistency for face quads
|
||||
} else {
|
||||
cullFace(null);
|
||||
}
|
||||
|
||||
nominalFace(nominalFace);
|
||||
switch(nominalFace)
|
||||
{
|
||||
case UP:
|
||||
depth = 1 - depth;
|
||||
top = 1 - top;
|
||||
bottom = 1 - bottom;
|
||||
default QuadEmitter spriteUnitSquare(int spriteIndex) {
|
||||
sprite(0, spriteIndex, 0, 0);
|
||||
sprite(1, spriteIndex, 0, 1);
|
||||
sprite(2, spriteIndex, 1, 1);
|
||||
sprite(3, spriteIndex, 1, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
case DOWN:
|
||||
pos(0, left, depth, top);
|
||||
pos(1, left, depth, bottom);
|
||||
pos(2, right, depth, bottom);
|
||||
pos(3, right, depth, top);
|
||||
break;
|
||||
@Override
|
||||
QuadEmitter spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
|
||||
|
||||
case EAST:
|
||||
depth = 1 - depth;
|
||||
left = 1 - left;
|
||||
right = 1 - right;
|
||||
/**
|
||||
* Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)}
|
||||
* is effectively zero - meaning the face is a cull face.
|
||||
*/
|
||||
final float CULL_FACE_EPSILON = 0.00001f;
|
||||
|
||||
case WEST:
|
||||
pos(0, depth, top, left);
|
||||
pos(1, depth, bottom, left);
|
||||
pos(2, depth, bottom, right);
|
||||
pos(3, depth, top, right);
|
||||
break;
|
||||
/**
|
||||
* Helper method to assign vertex coordinates for a square aligned with the given face.
|
||||
* Ensures that vertex order is consistent with vanilla convention. (Incorrect order can
|
||||
* lead to bad AO lighting unless enhanced lighting logic is available/enabled.)<p>
|
||||
*
|
||||
* Square will be parallel to the given face and coplanar with the face (and culled if the
|
||||
* face is occluded) if the depth parameter is approximately zero. See {@link #CULL_FACE_EPSILON}.<p>
|
||||
*
|
||||
* All coordinates should be normalized (0-1).
|
||||
*/
|
||||
default QuadEmitter square(Direction nominalFace, float left, float bottom, float right, float top, float depth) {
|
||||
if (Math.abs(depth) < CULL_FACE_EPSILON) {
|
||||
cullFace(nominalFace);
|
||||
depth = 0; // avoid any inconsistency for face quads
|
||||
} else {
|
||||
cullFace(null);
|
||||
}
|
||||
|
||||
case SOUTH:
|
||||
depth = 1 - depth;
|
||||
left = 1 - left;
|
||||
right = 1 - right;
|
||||
|
||||
case NORTH:
|
||||
pos(0, right, top, depth);
|
||||
pos(1, right, bottom, depth);
|
||||
pos(2, left, bottom, depth);
|
||||
pos(3, left, top, depth);
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In static mesh building, causes quad to be appended to the mesh being built.
|
||||
* In a dynamic render context, create a new quad to be output to rendering.
|
||||
* In both cases, current instance is reset to default values.
|
||||
*/
|
||||
QuadEmitter emit();
|
||||
nominalFace(nominalFace);
|
||||
switch (nominalFace) {
|
||||
case UP:
|
||||
depth = 1 - depth;
|
||||
top = 1 - top;
|
||||
bottom = 1 - bottom;
|
||||
|
||||
case DOWN:
|
||||
pos(0, left, depth, top);
|
||||
pos(1, left, depth, bottom);
|
||||
pos(2, right, depth, bottom);
|
||||
pos(3, right, depth, top);
|
||||
break;
|
||||
|
||||
case EAST:
|
||||
depth = 1 - depth;
|
||||
left = 1 - left;
|
||||
right = 1 - right;
|
||||
|
||||
case WEST:
|
||||
pos(0, depth, top, left);
|
||||
pos(1, depth, bottom, left);
|
||||
pos(2, depth, bottom, right);
|
||||
pos(3, depth, top, right);
|
||||
break;
|
||||
|
||||
case SOUTH:
|
||||
depth = 1 - depth;
|
||||
left = 1 - left;
|
||||
right = 1 - right;
|
||||
|
||||
case NORTH:
|
||||
pos(0, right, top, depth);
|
||||
pos(1, right, bottom, depth);
|
||||
pos(2, left, bottom, depth);
|
||||
pos(3, left, top, depth);
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In static mesh building, causes quad to be appended to the mesh being built.
|
||||
* In a dynamic render context, create a new quad to be output to rendering.
|
||||
* In both cases, current instance is reset to default values.
|
||||
*/
|
||||
QuadEmitter emit();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package net.fabricmc.fabric.api.renderer.v1.mesh;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||
import net.minecraft.client.render.VertexFormats;
|
||||
import net.minecraft.client.render.model.BakedQuad;
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.client.util.math.Vector3f;
|
||||
|
@ -30,173 +31,179 @@ import net.minecraft.util.math.Direction;
|
|||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
public interface QuadView {
|
||||
/**
|
||||
* Reads baked vertex data and outputs standard baked quad
|
||||
* vertex data in the given array and location.<p>
|
||||
*
|
||||
* @param spriteIndex The sprite to be used for the quad.
|
||||
* Behavior for values > 0 is currently undefined.
|
||||
*
|
||||
* @param target Target array for the baked quad data.
|
||||
*
|
||||
* @param targetIndex Starting position in target array - array must have
|
||||
* at least 28 elements available at this index.
|
||||
*
|
||||
* @param isItem If true, will output vertex normals. Otherwise will output
|
||||
* lightmaps, per Minecraft vertex formats for baked models.
|
||||
*/
|
||||
void toVanilla(int spriteIndex, int[] target, int targetIndex, boolean isItem);
|
||||
|
||||
/**
|
||||
* Extracts all quad properties except material to the given {@link MutableQuadView} instance.
|
||||
* Must be used before calling {link QuadEmitter#emit()} on the target instance.
|
||||
* Meant for re-texturing, analysis and static transformation use cases.
|
||||
*/
|
||||
void copyTo(MutableQuadView target);
|
||||
|
||||
/**
|
||||
* Retrieves the material serialized with the quad.
|
||||
*/
|
||||
RenderMaterial material();
|
||||
|
||||
/**
|
||||
* Retrieves the quad color index serialized with the quad.
|
||||
*/
|
||||
int colorIndex();
|
||||
|
||||
/**
|
||||
* Equivalent to {@link BakedQuad#getFace()}. This is the face used for vanilla lighting
|
||||
* calculations and will be the block face to which the quad is most closely aligned. Always
|
||||
* the same as cull face for quads that are on a block face, but never null.<p>
|
||||
*/
|
||||
Direction lightFace();
|
||||
|
||||
/**
|
||||
* If non-null, quad should not be rendered in-world if the
|
||||
* opposite face of a neighbor block occludes it.<p>
|
||||
*
|
||||
* See {@link MutableQuadView#cullFace(Direction)}.
|
||||
*/
|
||||
Direction cullFace();
|
||||
|
||||
/**
|
||||
* See {@link MutableQuadView#nominalFace(Direction)}.
|
||||
*/
|
||||
Direction nominalFace();
|
||||
|
||||
/**
|
||||
* Normal of the quad as implied by geometry. Will be invalid
|
||||
* if quad vertices are not co-planar. Typically computed lazily
|
||||
* on demand and not encoded.<p>
|
||||
*
|
||||
* Not typically needed by models. Exposed to enable standard lighting
|
||||
* utility functions for use by renderers.
|
||||
*/
|
||||
Vector3f faceNormal();
|
||||
|
||||
/**
|
||||
* Generates a new BakedQuad instance with texture
|
||||
* coordinates and colors from the given sprite.<p>
|
||||
*
|
||||
* param source Data previously packed by {@link MeshBuilder}.
|
||||
*
|
||||
* param sourceIndex Index where packed data starts.
|
||||
*
|
||||
* @param spriteIndex The sprite to be used for the quad.
|
||||
* Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*
|
||||
* @param sprite {@link MutableQuadView} does not serialize sprites
|
||||
* so the sprite must be provided by the caller.
|
||||
*
|
||||
* @param isItem If true, will output vertex normals. Otherwise will output
|
||||
* lightmaps, per Minecraft vertex formats for baked models.
|
||||
*
|
||||
* @return A new baked quad instance with the closest-available appearance
|
||||
* supported by vanilla features. Will retain emissive light maps, for example,
|
||||
* but the standard Minecraft renderer will not use them.
|
||||
*/
|
||||
default BakedQuad toBakedQuad(int spriteIndex, Sprite sprite, boolean isItem) {
|
||||
int vertexData[] = new int[28];
|
||||
toVanilla(spriteIndex, vertexData, 0, isItem);
|
||||
return new BakedQuad(vertexData, colorIndex(), lightFace(), sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the integer tag encoded with this quad via {@link MutableQuadView#tag(int)}.
|
||||
* Will return zero if no tag was set. For use by models.
|
||||
*/
|
||||
int tag();
|
||||
|
||||
/**
|
||||
* Pass a non-null target to avoid allocation - will be returned with values.
|
||||
* Otherwise returns a new instance.
|
||||
*/
|
||||
Vector3f copyPos(int vertexIndex, Vector3f target);
|
||||
|
||||
/**
|
||||
* Convenience: access x, y, z by index 0-2
|
||||
*/
|
||||
float posByIndex(int vertexIndex, int coordinateIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, x coordinate.
|
||||
*/
|
||||
float x(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, y coordinate.
|
||||
*/
|
||||
float y(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, z coordinate.
|
||||
*/
|
||||
float z(int vertexIndex);
|
||||
|
||||
/**
|
||||
* If false, no vertex normal was provided.
|
||||
* Lighting should use face normal in that case.
|
||||
*/
|
||||
boolean hasNormal(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Pass a non-null target to avoid allocation - will be returned with values.
|
||||
* Otherwise returns a new instance. Returns null if normal not present.
|
||||
*/
|
||||
Vector3f copyNormal(int vertexIndex, Vector3f target);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalX(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalY(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalZ(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Minimum block brightness. Zero if not set.
|
||||
*/
|
||||
int lightmap(int vertexIndex);
|
||||
/** Count of integers in a conventional (un-modded) block or item vertex */
|
||||
int VANILLA_VERTEX_STRIDE = VertexFormats.POSITION_COLOR_UV_NORMAL.getVertexSizeInteger();
|
||||
|
||||
/**
|
||||
* Retrieve vertex color.
|
||||
*/
|
||||
int spriteColor(int vertexIndex, int spriteIndex);
|
||||
|
||||
/**
|
||||
* Retrieve horizontal sprite atlas coordinates.
|
||||
*/
|
||||
float spriteU(int vertexIndex, int spriteIndex);
|
||||
|
||||
/**
|
||||
* Retrieve vertical sprite atlas coordinates
|
||||
*/
|
||||
float spriteV(int vertexIndex, int spriteIndex);
|
||||
/** Count of integers in a conventional (un-modded) block or item quad */
|
||||
int VANILLA_QUAD_STRIDE = VANILLA_VERTEX_STRIDE * 4;
|
||||
|
||||
/**
|
||||
* Reads baked vertex data and outputs standard baked quad
|
||||
* vertex data in the given array and location.<p>
|
||||
*
|
||||
* @param spriteIndex The sprite to be used for the quad.
|
||||
* Behavior for values > 0 is currently undefined.
|
||||
*
|
||||
* @param target Target array for the baked quad data.
|
||||
*
|
||||
* @param targetIndex Starting position in target array - array must have
|
||||
* at least 28 elements available at this index.
|
||||
*
|
||||
* @param isItem If true, will output vertex normals. Otherwise will output
|
||||
* lightmaps, per Minecraft vertex formats for baked models.
|
||||
*/
|
||||
void toVanilla(int spriteIndex, int[] target, int targetIndex, boolean isItem);
|
||||
|
||||
/**
|
||||
* Extracts all quad properties except material to the given {@link MutableQuadView} instance.
|
||||
* Must be used before calling {link QuadEmitter#emit()} on the target instance.
|
||||
* Meant for re-texturing, analysis and static transformation use cases.
|
||||
*/
|
||||
void copyTo(MutableQuadView target);
|
||||
|
||||
/**
|
||||
* Retrieves the material serialized with the quad.
|
||||
*/
|
||||
RenderMaterial material();
|
||||
|
||||
/**
|
||||
* Retrieves the quad color index serialized with the quad.
|
||||
*/
|
||||
int colorIndex();
|
||||
|
||||
/**
|
||||
* Equivalent to {@link BakedQuad#getFace()}. This is the face used for vanilla lighting
|
||||
* calculations and will be the block face to which the quad is most closely aligned. Always
|
||||
* the same as cull face for quads that are on a block face, but never null.<p>
|
||||
*/
|
||||
Direction lightFace();
|
||||
|
||||
/**
|
||||
* If non-null, quad should not be rendered in-world if the
|
||||
* opposite face of a neighbor block occludes it.<p>
|
||||
*
|
||||
* See {@link MutableQuadView#cullFace(Direction)}.
|
||||
*/
|
||||
Direction cullFace();
|
||||
|
||||
/**
|
||||
* See {@link MutableQuadView#nominalFace(Direction)}.
|
||||
*/
|
||||
Direction nominalFace();
|
||||
|
||||
/**
|
||||
* Normal of the quad as implied by geometry. Will be invalid
|
||||
* if quad vertices are not co-planar. Typically computed lazily
|
||||
* on demand and not encoded.<p>
|
||||
*
|
||||
* Not typically needed by models. Exposed to enable standard lighting
|
||||
* utility functions for use by renderers.
|
||||
*/
|
||||
Vector3f faceNormal();
|
||||
|
||||
/**
|
||||
* Generates a new BakedQuad instance with texture
|
||||
* coordinates and colors from the given sprite.<p>
|
||||
*
|
||||
* param source Data previously packed by {@link MeshBuilder}.
|
||||
*
|
||||
* param sourceIndex Index where packed data starts.
|
||||
*
|
||||
* @param spriteIndex The sprite to be used for the quad.
|
||||
* Behavior for {@code spriteIndex > 0} is currently undefined.
|
||||
*
|
||||
* @param sprite {@link MutableQuadView} does not serialize sprites
|
||||
* so the sprite must be provided by the caller.
|
||||
*
|
||||
* @param isItem If true, will output vertex normals. Otherwise will output
|
||||
* lightmaps, per Minecraft vertex formats for baked models.
|
||||
*
|
||||
* @return A new baked quad instance with the closest-available appearance
|
||||
* supported by vanilla features. Will retain emissive light maps, for example,
|
||||
* but the standard Minecraft renderer will not use them.
|
||||
*/
|
||||
default BakedQuad toBakedQuad(int spriteIndex, Sprite sprite, boolean isItem) {
|
||||
int vertexData[] = new int[VANILLA_QUAD_STRIDE];
|
||||
toVanilla(spriteIndex, vertexData, 0, isItem);
|
||||
return new BakedQuad(vertexData, colorIndex(), lightFace(), sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the integer tag encoded with this quad via {@link MutableQuadView#tag(int)}.
|
||||
* Will return zero if no tag was set. For use by models.
|
||||
*/
|
||||
int tag();
|
||||
|
||||
/**
|
||||
* Pass a non-null target to avoid allocation - will be returned with values.
|
||||
* Otherwise returns a new instance.
|
||||
*/
|
||||
Vector3f copyPos(int vertexIndex, Vector3f target);
|
||||
|
||||
/**
|
||||
* Convenience: access x, y, z by index 0-2
|
||||
*/
|
||||
float posByIndex(int vertexIndex, int coordinateIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, x coordinate.
|
||||
*/
|
||||
float x(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, y coordinate.
|
||||
*/
|
||||
float y(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Geometric position, z coordinate.
|
||||
*/
|
||||
float z(int vertexIndex);
|
||||
|
||||
/**
|
||||
* If false, no vertex normal was provided.
|
||||
* Lighting should use face normal in that case.
|
||||
*/
|
||||
boolean hasNormal(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Pass a non-null target to avoid allocation - will be returned with values.
|
||||
* Otherwise returns a new instance. Returns null if normal not present.
|
||||
*/
|
||||
Vector3f copyNormal(int vertexIndex, Vector3f target);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalX(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalY(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Will return {@link Float#NaN} if normal not present.
|
||||
*/
|
||||
float normalZ(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Minimum block brightness. Zero if not set.
|
||||
*/
|
||||
int lightmap(int vertexIndex);
|
||||
|
||||
/**
|
||||
* Retrieve vertex color.
|
||||
*/
|
||||
int spriteColor(int vertexIndex, int spriteIndex);
|
||||
|
||||
/**
|
||||
* Retrieve horizontal sprite atlas coordinates.
|
||||
*/
|
||||
float spriteU(int vertexIndex, int spriteIndex);
|
||||
|
||||
/**
|
||||
* Retrieve vertical sprite atlas coordinates
|
||||
*/
|
||||
float spriteV(int vertexIndex, int spriteIndex);
|
||||
}
|
||||
|
|
|
@ -38,87 +38,87 @@ import net.minecraft.world.BlockRenderView;
|
|||
* This means any BakedModel instance can be safely cast to this interface without an instanceof check.
|
||||
*/
|
||||
public interface FabricBakedModel {
|
||||
/**
|
||||
* When true, signals renderer this producer is implemented through {@link BakedModel#getQuads(BlockState, net.minecraft.util.math.Direction, Random)}.
|
||||
* Also means the model does not rely on any non-vanilla features.
|
||||
* Allows the renderer to optimize or route vanilla models through the unmodified vanilla pipeline if desired.<p>
|
||||
/**
|
||||
* When true, signals renderer this producer is implemented through {@link BakedModel#getQuads(BlockState, net.minecraft.util.math.Direction, Random)}.
|
||||
* Also means the model does not rely on any non-vanilla features.
|
||||
* Allows the renderer to optimize or route vanilla models through the unmodified vanilla pipeline if desired.<p>
|
||||
|
||||
* Fabric overrides to true for vanilla baked models.
|
||||
* Enhanced models that use this API should return false,
|
||||
* otherwise the API will not recognize the model. <p>
|
||||
*/
|
||||
boolean isVanillaAdapter();
|
||||
|
||||
* Fabric overrides to true for vanilla baked models.
|
||||
* Enhanced models that use this API should return false,
|
||||
* otherwise the API will not recognize the model. <p>
|
||||
*/
|
||||
boolean isVanillaAdapter();
|
||||
|
||||
/**
|
||||
* This method will be called during chunk rebuilds to generate both the static and
|
||||
* dynamic portions of a block model when the model implements this interface and
|
||||
* {@link #isVanillaAdapter()} returns false. <p>
|
||||
*
|
||||
* During chunk rebuild, this method will always be called exactly one time per block
|
||||
* position, irrespective of which or how many faces or block render layers are included
|
||||
* in the model. Models must output all quads/meshes in a single pass.<p>
|
||||
*
|
||||
* Also called to render block models outside of chunk rebuild or block entity rendering.
|
||||
* Typically this happens when the block is being rendered as an entity, not as a block placed in the world.
|
||||
* Currently this happens for falling blocks and blocks being pushed by a piston, but renderers
|
||||
* should invoke this for all calls to {@link BlockModelRenderer#tesselate(ExtendedBlockView, BakedModel, BlockState, BlockPos, net.minecraft.client.render.BufferBuilder, boolean, Random, long)}
|
||||
* that occur outside of chunk rebuilds to allow for features added by mods, unless
|
||||
* {@link #isVanillaAdapter()} returns true.<p>
|
||||
*
|
||||
* Outside of chunk rebuilds, this method will be called every frame. Model implementations should
|
||||
* rely on pre-baked meshes as much as possible and keep transformation to a minimum. The provided
|
||||
* block position may be the <em>nearest</em> block position and not actual. For this reason, neighbor
|
||||
* state lookups are best avoided or will require special handling. Block entity lookups are
|
||||
* likely to fail and/or give meaningless results.<p>
|
||||
*
|
||||
* In all cases, renderer will handle face occlusion and filter quads on faces obscured by
|
||||
* neighboring blocks (if appropriate). Models only need to consider "sides" to the
|
||||
* extent the model is driven by connection with neighbor blocks or other world state.<p>
|
||||
*
|
||||
* Note: with {@link BakedModel#getQuads(BlockState, net.minecraft.util.math.Direction, Random)}, the random
|
||||
* parameter is normally initialized with the same seed prior to each face layer.
|
||||
* Model authors should note this method is called only once per block, and call the provided
|
||||
* Random supplier multiple times if re-seeding is necessary. For wrapped vanilla baked models,
|
||||
* it will probably be easier to use {@link RenderContext#fallbackConsumer} which handles
|
||||
* re-seeding per face automatically.<p>
|
||||
*
|
||||
* @param blockView Access to world state. Using {@link net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView#getBlockEntityRenderAttachment(BlockPos)} to
|
||||
* retrieve block entity state unless thread safety can be guaranteed.
|
||||
* param safeBlockEntityAccessor Thread-safe access to block entity data
|
||||
* @param state Block state for model being rendered.
|
||||
* @param pos Position of block for model being rendered.
|
||||
* @param randomSupplier Random object seeded per vanilla conventions. Call multiple times to re-seed.
|
||||
* Will not be thread-safe. Do not cache or retain a reference.
|
||||
* @param context Accepts model output.
|
||||
*/
|
||||
void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context);
|
||||
/**
|
||||
* This method will be called during chunk rebuilds to generate both the static and
|
||||
* dynamic portions of a block model when the model implements this interface and
|
||||
* {@link #isVanillaAdapter()} returns false. <p>
|
||||
*
|
||||
* During chunk rebuild, this method will always be called exactly one time per block
|
||||
* position, irrespective of which or how many faces or block render layers are included
|
||||
* in the model. Models must output all quads/meshes in a single pass.<p>
|
||||
*
|
||||
* Also called to render block models outside of chunk rebuild or block entity rendering.
|
||||
* Typically this happens when the block is being rendered as an entity, not as a block placed in the world.
|
||||
* Currently this happens for falling blocks and blocks being pushed by a piston, but renderers
|
||||
* should invoke this for all calls to {@link BlockModelRenderer#tesselate(ExtendedBlockView, BakedModel, BlockState, BlockPos, net.minecraft.client.render.BufferBuilder, boolean, Random, long)}
|
||||
* that occur outside of chunk rebuilds to allow for features added by mods, unless
|
||||
* {@link #isVanillaAdapter()} returns true.<p>
|
||||
*
|
||||
* Outside of chunk rebuilds, this method will be called every frame. Model implementations should
|
||||
* rely on pre-baked meshes as much as possible and keep transformation to a minimum. The provided
|
||||
* block position may be the <em>nearest</em> block position and not actual. For this reason, neighbor
|
||||
* state lookups are best avoided or will require special handling. Block entity lookups are
|
||||
* likely to fail and/or give meaningless results.<p>
|
||||
*
|
||||
* In all cases, renderer will handle face occlusion and filter quads on faces obscured by
|
||||
* neighboring blocks (if appropriate). Models only need to consider "sides" to the
|
||||
* extent the model is driven by connection with neighbor blocks or other world state.<p>
|
||||
*
|
||||
* Note: with {@link BakedModel#getQuads(BlockState, net.minecraft.util.math.Direction, Random)}, the random
|
||||
* parameter is normally initialized with the same seed prior to each face layer.
|
||||
* Model authors should note this method is called only once per block, and call the provided
|
||||
* Random supplier multiple times if re-seeding is necessary. For wrapped vanilla baked models,
|
||||
* it will probably be easier to use {@link RenderContext#fallbackConsumer} which handles
|
||||
* re-seeding per face automatically.<p>
|
||||
*
|
||||
* @param blockView Access to world state. Using {@link net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView#getBlockEntityRenderAttachment(BlockPos)} to
|
||||
* retrieve block entity state unless thread safety can be guaranteed.
|
||||
* param safeBlockEntityAccessor Thread-safe access to block entity data
|
||||
* @param state Block state for model being rendered.
|
||||
* @param pos Position of block for model being rendered.
|
||||
* @param randomSupplier Random object seeded per vanilla conventions. Call multiple times to re-seed.
|
||||
* Will not be thread-safe. Do not cache or retain a reference.
|
||||
* @param context Accepts model output.
|
||||
*/
|
||||
void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context);
|
||||
|
||||
/**
|
||||
* This method will be called during item rendering to generate both the static and
|
||||
* dynamic portions of an item model when the model implements this interface and
|
||||
* {@link #isVanillaAdapter()} returns false.<p>
|
||||
*
|
||||
* Vanilla item rendering is normally very limited. It ignores lightmaps, vertex colors,
|
||||
* and vertex normals. Renderers are expected to implement enhanced features for item
|
||||
* models. If a feature is impractical due to performance or other concerns, then the
|
||||
* renderer must at least give acceptable visual results without the need for special-
|
||||
* case handling in model implementations.<p>
|
||||
*
|
||||
* Calls to this method will generally happen on the main client thread but nothing
|
||||
* prevents a mod or renderer from calling this method concurrently. Implementations
|
||||
* should not mutate the ItemStack parameter, and best practice will be to make the
|
||||
* method thread-safe.<p>
|
||||
*
|
||||
* Implementing this method does NOT mitigate the need to implement a functional
|
||||
* {@link BakedModel#getItemPropertyOverrides()} method, because this method will be called
|
||||
* on the <em>result</em> of {@link BakedModel#getItemPropertyOverrides}. However, that
|
||||
* method can simply return the base model because the output from this method will
|
||||
* be used for rendering.<p>
|
||||
*
|
||||
* Renderer implementations should also use this method to obtain the quads used
|
||||
* for item enchantment glint rendering. This means models can put geometric variation
|
||||
* logic here, instead of returning every possible shape from {@link BakedModel#getItemPropertyOverrides}
|
||||
* as vanilla baked models.
|
||||
*/
|
||||
void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context);
|
||||
/**
|
||||
* This method will be called during item rendering to generate both the static and
|
||||
* dynamic portions of an item model when the model implements this interface and
|
||||
* {@link #isVanillaAdapter()} returns false.<p>
|
||||
*
|
||||
* Vanilla item rendering is normally very limited. It ignores lightmaps, vertex colors,
|
||||
* and vertex normals. Renderers are expected to implement enhanced features for item
|
||||
* models. If a feature is impractical due to performance or other concerns, then the
|
||||
* renderer must at least give acceptable visual results without the need for special-
|
||||
* case handling in model implementations.<p>
|
||||
*
|
||||
* Calls to this method will generally happen on the main client thread but nothing
|
||||
* prevents a mod or renderer from calling this method concurrently. Implementations
|
||||
* should not mutate the ItemStack parameter, and best practice will be to make the
|
||||
* method thread-safe.<p>
|
||||
*
|
||||
* Implementing this method does NOT mitigate the need to implement a functional
|
||||
* {@link BakedModel#getItemPropertyOverrides()} method, because this method will be called
|
||||
* on the <em>result</em> of {@link BakedModel#getItemPropertyOverrides}. However, that
|
||||
* method can simply return the base model because the output from this method will
|
||||
* be used for rendering.<p>
|
||||
*
|
||||
* Renderer implementations should also use this method to obtain the quads used
|
||||
* for item enchantment glint rendering. This means models can put geometric variation
|
||||
* logic here, instead of returning every possible shape from {@link BakedModel#getItemPropertyOverrides}
|
||||
* as vanilla baked models.
|
||||
*/
|
||||
void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context);
|
||||
}
|
||||
|
|
|
@ -38,56 +38,56 @@ import net.minecraft.world.BlockRenderView;
|
|||
* Avoids boilerplate code for pass-through methods. For example usage see {@link DamageModel}.
|
||||
*/
|
||||
public abstract class ForwardingBakedModel implements BakedModel, FabricBakedModel {
|
||||
/** implementations must set this somehow */
|
||||
protected BakedModel wrapped;
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
((FabricBakedModel)wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
}
|
||||
/** implementations must set this somehow */
|
||||
protected BakedModel wrapped;
|
||||
|
||||
@Override
|
||||
public boolean isVanillaAdapter() {
|
||||
return ((FabricBakedModel)wrapped).isVanillaAdapter();
|
||||
}
|
||||
@Override
|
||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
((FabricBakedModel) wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
((FabricBakedModel)wrapped).emitItemQuads(stack, randomSupplier, context);
|
||||
}
|
||||
@Override
|
||||
public boolean isVanillaAdapter() {
|
||||
return ((FabricBakedModel) wrapped).isVanillaAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(BlockState blockState, Direction face, Random rand) {
|
||||
return wrapped.getQuads(blockState, face, rand);
|
||||
}
|
||||
@Override
|
||||
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
((FabricBakedModel) wrapped).emitItemQuads(stack, randomSupplier, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAmbientOcclusion() {
|
||||
return wrapped.useAmbientOcclusion();
|
||||
}
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(BlockState blockState, Direction face, Random rand) {
|
||||
return wrapped.getQuads(blockState, face, rand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDepthInGui() {
|
||||
return wrapped.hasDepthInGui();
|
||||
}
|
||||
@Override
|
||||
public boolean useAmbientOcclusion() {
|
||||
return wrapped.useAmbientOcclusion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuiltin() {
|
||||
return wrapped.isBuiltin();
|
||||
}
|
||||
@Override
|
||||
public boolean hasDepthInGui() {
|
||||
return wrapped.hasDepthInGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sprite getSprite() {
|
||||
return wrapped.getSprite();
|
||||
}
|
||||
@Override
|
||||
public boolean isBuiltin() {
|
||||
return wrapped.isBuiltin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelTransformation getTransformation() {
|
||||
return wrapped.getTransformation();
|
||||
}
|
||||
@Override
|
||||
public Sprite getSprite() {
|
||||
return wrapped.getSprite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelItemPropertyOverrideList getItemPropertyOverrides() {
|
||||
return wrapped.getItemPropertyOverrides();
|
||||
}
|
||||
@Override
|
||||
public ModelTransformation getTransformation() {
|
||||
return wrapped.getTransformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelItemPropertyOverrideList getItemPropertyOverrides() {
|
||||
return wrapped.getItemPropertyOverrides();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,104 +33,91 @@ import net.minecraft.util.math.Direction;
|
|||
* Collection of utilities for model implementations.
|
||||
*/
|
||||
public abstract class ModelHelper {
|
||||
private ModelHelper() {}
|
||||
|
||||
/** Result from {@link #toFaceIndex(Direction)} for null values. */
|
||||
public static final int NULL_FACE_ID = 6;
|
||||
|
||||
/**
|
||||
* Convenient way to encode faces that may be null.
|
||||
* Null is returned as {@link #NULL_FACE_ID}.
|
||||
* Use {@link #faceFromIndex(int)} to retrieve encoded face.
|
||||
*/
|
||||
public static int toFaceIndex(Direction face) {
|
||||
return face == null ? NULL_FACE_ID : face.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to decode a result from {@link #toFaceIndex(Direction)}.
|
||||
* Return value will be null if encoded value was null.
|
||||
* Can also be used for no-allocation iteration of {@link Direction#values()},
|
||||
* optionally including the null face. (Use < or <= {@link #NULL_FACE_ID}
|
||||
* to exclude or include the null value, respectively.)
|
||||
*/
|
||||
public static Direction faceFromIndex(int faceIndex) {
|
||||
return FACES[faceIndex];
|
||||
}
|
||||
|
||||
/** see {@link #faceFromIndex(int)} */
|
||||
private static final Direction[] FACES = Arrays.copyOf(Direction.values(), 7);
|
||||
|
||||
/**
|
||||
* Converts a mesh into an array of lists of vanilla baked quads.
|
||||
* Useful for creating vanilla baked models when required for compatibility.
|
||||
* The array indexes correspond to {@link Direction#getId()} with the
|
||||
* addition of {@link #NULL_FACE_ID}.<p>
|
||||
*
|
||||
* Retrieves sprites from the block texture atlas via {@link SpriteFinder}.
|
||||
*/
|
||||
public static List<BakedQuad>[] toQuadLists(Mesh mesh) {
|
||||
SpriteFinder finder = SpriteFinder.get(MinecraftClient.getInstance().getSpriteAtlas());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final ImmutableList.Builder<BakedQuad>[] builders = new ImmutableList.Builder[7];
|
||||
for(int i = 0; i < 7; i++) {
|
||||
builders[i] = ImmutableList.builder();
|
||||
}
|
||||
|
||||
mesh.forEach(q -> {
|
||||
final int limit = q.material().spriteDepth();
|
||||
for(int l = 0; l < limit; l++) {
|
||||
Direction face = q.cullFace();
|
||||
builders[face == null ? 6 : face.getId()].add(q.toBakedQuad(l, finder.find(q, l), false));
|
||||
}
|
||||
}) ;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BakedQuad>[] result = new List[7];
|
||||
for(int i = 0; i < 7; i++) {
|
||||
result[i] = builders[i].build();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private ModelHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The vanilla model transformation logic is closely coupled with model deserialization.
|
||||
* That does little good for modded model loaders and procedurally generated models.
|
||||
* This convenient construction method applies the same scaling factors used for vanilla models.
|
||||
* This means you can use values from a vanilla JSON file as inputs to this method.
|
||||
*/
|
||||
private static Transformation makeTransform(
|
||||
float rotationX, float rotationY, float rotationZ,
|
||||
float translationX, float translationY, float translationZ,
|
||||
float scaleX, float scaleY, float scaleZ) {
|
||||
Vector3f translation = new Vector3f(translationX, translationY, translationZ);
|
||||
translation.scale(0.0625f);
|
||||
translation.clamp(-5.0F, 5.0F);
|
||||
return new Transformation(
|
||||
new Vector3f(rotationX, rotationY, rotationZ),
|
||||
translation,
|
||||
new Vector3f(scaleX, scaleY, scaleZ));
|
||||
}
|
||||
|
||||
public static final Transformation TRANSFORM_BLOCK_GUI = makeTransform(30, 225, 0, 0, 0, 0, 0.625f, 0.625f, 0.625f);
|
||||
public static final Transformation TRANSFORM_BLOCK_GROUND = makeTransform(0, 0, 0, 0, 3, 0, 0.25f, 0.25f, 0.25f);
|
||||
public static final Transformation TRANSFORM_BLOCK_FIXED = makeTransform(0, 0, 0, 0, 0, 0, 0.5f, 0.5f, 0.5f);
|
||||
public static final Transformation TRANSFORM_BLOCK_3RD_PERSON_RIGHT = makeTransform(75, 45, 0, 0, 2.5f, 0, 0.375f, 0.375f, 0.375f);
|
||||
public static final Transformation TRANSFORM_BLOCK_1ST_PERSON_RIGHT = makeTransform(0, 45, 0, 0, 0, 0, 0.4f, 0.4f, 0.4f);
|
||||
public static final Transformation TRANSFORM_BLOCK_1ST_PERSON_LEFT = makeTransform(0, 225, 0, 0, 0, 0, 0.4f, 0.4f, 0.4f);
|
||||
|
||||
/**
|
||||
* Mimics the vanilla model transformation used for most vanilla blocks,
|
||||
* and should be suitable for most custom block-like models.
|
||||
*/
|
||||
public static final ModelTransformation MODEL_TRANSFORM_BLOCK = new ModelTransformation(
|
||||
TRANSFORM_BLOCK_3RD_PERSON_RIGHT,
|
||||
TRANSFORM_BLOCK_3RD_PERSON_RIGHT,
|
||||
TRANSFORM_BLOCK_1ST_PERSON_LEFT,
|
||||
TRANSFORM_BLOCK_1ST_PERSON_RIGHT,
|
||||
Transformation.NONE,
|
||||
TRANSFORM_BLOCK_GUI,
|
||||
TRANSFORM_BLOCK_GROUND,
|
||||
TRANSFORM_BLOCK_FIXED);
|
||||
/** Result from {@link #toFaceIndex(Direction)} for null values. */
|
||||
public static final int NULL_FACE_ID = 6;
|
||||
|
||||
/**
|
||||
* Convenient way to encode faces that may be null.
|
||||
* Null is returned as {@link #NULL_FACE_ID}.
|
||||
* Use {@link #faceFromIndex(int)} to retrieve encoded face.
|
||||
*/
|
||||
public static int toFaceIndex(Direction face) {
|
||||
return face == null ? NULL_FACE_ID : face.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to decode a result from {@link #toFaceIndex(Direction)}.
|
||||
* Return value will be null if encoded value was null.
|
||||
* Can also be used for no-allocation iteration of {@link Direction#values()},
|
||||
* optionally including the null face. (Use < or <= {@link #NULL_FACE_ID}
|
||||
* to exclude or include the null value, respectively.)
|
||||
*/
|
||||
public static Direction faceFromIndex(int faceIndex) {
|
||||
return FACES[faceIndex];
|
||||
}
|
||||
|
||||
/** see {@link #faceFromIndex(int)} */
|
||||
private static final Direction[] FACES = Arrays.copyOf(Direction.values(), 7);
|
||||
|
||||
/**
|
||||
* Converts a mesh into an array of lists of vanilla baked quads.
|
||||
* Useful for creating vanilla baked models when required for compatibility.
|
||||
* The array indexes correspond to {@link Direction#getId()} with the
|
||||
* addition of {@link #NULL_FACE_ID}.<p>
|
||||
*
|
||||
* Retrieves sprites from the block texture atlas via {@link SpriteFinder}.
|
||||
*/
|
||||
public static List<BakedQuad>[] toQuadLists(Mesh mesh) {
|
||||
SpriteFinder finder = SpriteFinder.get(MinecraftClient.getInstance().getSpriteAtlas());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final ImmutableList.Builder<BakedQuad>[] builders = new ImmutableList.Builder[7];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
builders[i] = ImmutableList.builder();
|
||||
}
|
||||
|
||||
mesh.forEach(q -> {
|
||||
final int limit = q.material().spriteDepth();
|
||||
for (int l = 0; l < limit; l++) {
|
||||
Direction face = q.cullFace();
|
||||
builders[face == null ? 6 : face.getId()].add(q.toBakedQuad(l, finder.find(q, l), false));
|
||||
}
|
||||
});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BakedQuad>[] result = new List[7];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
result[i] = builders[i].build();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vanilla model transformation logic is closely coupled with model deserialization.
|
||||
* That does little good for modded model loaders and procedurally generated models.
|
||||
* This convenient construction method applies the same scaling factors used for vanilla models.
|
||||
* This means you can use values from a vanilla JSON file as inputs to this method.
|
||||
*/
|
||||
private static Transformation makeTransform(float rotationX, float rotationY, float rotationZ, float translationX, float translationY, float translationZ, float scaleX, float scaleY, float scaleZ) {
|
||||
Vector3f translation = new Vector3f(translationX, translationY, translationZ);
|
||||
translation.scale(0.0625f);
|
||||
translation.clamp(-5.0F, 5.0F);
|
||||
return new Transformation(new Vector3f(rotationX, rotationY, rotationZ), translation, new Vector3f(scaleX, scaleY, scaleZ));
|
||||
}
|
||||
|
||||
public static final Transformation TRANSFORM_BLOCK_GUI = makeTransform(30, 225, 0, 0, 0, 0, 0.625f, 0.625f, 0.625f);
|
||||
public static final Transformation TRANSFORM_BLOCK_GROUND = makeTransform(0, 0, 0, 0, 3, 0, 0.25f, 0.25f, 0.25f);
|
||||
public static final Transformation TRANSFORM_BLOCK_FIXED = makeTransform(0, 0, 0, 0, 0, 0, 0.5f, 0.5f, 0.5f);
|
||||
public static final Transformation TRANSFORM_BLOCK_3RD_PERSON_RIGHT = makeTransform(75, 45, 0, 0, 2.5f, 0, 0.375f, 0.375f, 0.375f);
|
||||
public static final Transformation TRANSFORM_BLOCK_1ST_PERSON_RIGHT = makeTransform(0, 45, 0, 0, 0, 0, 0.4f, 0.4f, 0.4f);
|
||||
public static final Transformation TRANSFORM_BLOCK_1ST_PERSON_LEFT = makeTransform(0, 225, 0, 0, 0, 0, 0.4f, 0.4f, 0.4f);
|
||||
|
||||
/**
|
||||
* Mimics the vanilla model transformation used for most vanilla blocks,
|
||||
* and should be suitable for most custom block-like models.
|
||||
*/
|
||||
public static final ModelTransformation MODEL_TRANSFORM_BLOCK = new ModelTransformation(TRANSFORM_BLOCK_3RD_PERSON_RIGHT, TRANSFORM_BLOCK_3RD_PERSON_RIGHT, TRANSFORM_BLOCK_1ST_PERSON_LEFT, TRANSFORM_BLOCK_1ST_PERSON_RIGHT, Transformation.NONE, TRANSFORM_BLOCK_GUI, TRANSFORM_BLOCK_GROUND, TRANSFORM_BLOCK_FIXED);
|
||||
}
|
||||
|
|
|
@ -31,37 +31,37 @@ import net.minecraft.client.texture.SpriteAtlasTexture;
|
|||
* supplies the sprite parameter for {@link QuadView#toBakedQuad(int, Sprite, boolean)}.
|
||||
*/
|
||||
public interface SpriteFinder {
|
||||
/**
|
||||
* Retrieves or creates the finder for the given atlas.
|
||||
* Instances should not be retained as fields or they must be
|
||||
* refreshed whenever there is a resource reload or other event
|
||||
* that causes atlas textures to be re-stitched.
|
||||
*/
|
||||
public static SpriteFinder get(SpriteAtlasTexture atlas) {
|
||||
return SpriteFinderImpl.get(atlas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the atlas sprite containing the vertex centroid of the quad.
|
||||
* Vertex centroid is essentially the mean u,v coordinate - the intent being
|
||||
* to find a point that is unambiguously inside the sprite (vs on an edge.)<p>
|
||||
*
|
||||
* Should be reliable for any convex quad or triangle. May fail for non-convex quads.
|
||||
* Note that all the above refers to u,v coordinates. Geometric vertex does not matter,
|
||||
* except to the extent it was used to determine u,v.
|
||||
*/
|
||||
Sprite find(QuadView quad, int textureIndex);
|
||||
/**
|
||||
* Retrieves or creates the finder for the given atlas.
|
||||
* Instances should not be retained as fields or they must be
|
||||
* refreshed whenever there is a resource reload or other event
|
||||
* that causes atlas textures to be re-stitched.
|
||||
*/
|
||||
public static SpriteFinder get(SpriteAtlasTexture atlas) {
|
||||
return SpriteFinderImpl.get(atlas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to {@link #find(QuadView, int)} when vertex centroid is already
|
||||
* known or unsuitable. Expects normalized (0-1) coordinates on the atlas texture,
|
||||
* which should already be the case for u,v values in vanilla baked quads and in
|
||||
* {@link QuadView} after calling {@link MutableQuadView#spriteBake(int, Sprite, int)}.<p>
|
||||
*
|
||||
* Coordinates must be in the sprite interior for reliable results. Generally will
|
||||
* be easier to use {@link #find(QuadView, int)} unless you know the vertex
|
||||
* centroid will somehow not be in the quad interior. This method will be slightly
|
||||
* faster if you already have the centroid or another appropriate value.
|
||||
*/
|
||||
Sprite find(float u, float v);
|
||||
/**
|
||||
* Finds the atlas sprite containing the vertex centroid of the quad.
|
||||
* Vertex centroid is essentially the mean u,v coordinate - the intent being
|
||||
* to find a point that is unambiguously inside the sprite (vs on an edge.)<p>
|
||||
*
|
||||
* Should be reliable for any convex quad or triangle. May fail for non-convex quads.
|
||||
* Note that all the above refers to u,v coordinates. Geometric vertex does not matter,
|
||||
* except to the extent it was used to determine u,v.
|
||||
*/
|
||||
Sprite find(QuadView quad, int textureIndex);
|
||||
|
||||
/**
|
||||
* Alternative to {@link #find(QuadView, int)} when vertex centroid is already
|
||||
* known or unsuitable. Expects normalized (0-1) coordinates on the atlas texture,
|
||||
* which should already be the case for u,v values in vanilla baked quads and in
|
||||
* {@link QuadView} after calling {@link MutableQuadView#spriteBake(int, Sprite, int)}.<p>
|
||||
*
|
||||
* Coordinates must be in the sprite interior for reliable results. Generally will
|
||||
* be easier to use {@link #find(QuadView, int)} unless you know the vertex
|
||||
* centroid will somehow not be in the quad interior. This method will be slightly
|
||||
* faster if you already have the centroid or another appropriate value.
|
||||
*/
|
||||
Sprite find(float u, float v);
|
||||
}
|
||||
|
|
|
@ -29,63 +29,63 @@ import net.minecraft.client.render.model.BakedModel;
|
|||
* This defines the instance made available to models for buffering vertex data at render time.
|
||||
*/
|
||||
public interface RenderContext {
|
||||
/**
|
||||
* Used by models to send vertex data previously baked via {@link MeshBuilder}.
|
||||
* The fastest option and preferred whenever feasible.
|
||||
*/
|
||||
Consumer<Mesh> meshConsumer();
|
||||
|
||||
/**
|
||||
* Fabric causes vanilla baked models to send themselves
|
||||
* via this interface. Can also be used by compound models that contain a mix
|
||||
* of vanilla baked models, packaged quads and/or dynamic elements.
|
||||
*/
|
||||
Consumer<BakedModel> fallbackConsumer();
|
||||
|
||||
/**
|
||||
* Returns a {@link QuadEmitter} instance that emits directly to the render buffer.
|
||||
* It remains necessary to call {@link QuadEmitter#emit()} to output the quad.<p>
|
||||
*
|
||||
* This method will always be less performant than passing pre-baked meshes
|
||||
* via {@link #meshConsumer()}. It should be used sparingly for model components that
|
||||
* demand it - text, icons, dynamic indicators, or other elements that vary too
|
||||
* much for static baking to be feasible.<p>
|
||||
*
|
||||
* Calling this method invalidates any {@link QuadEmitter} returned earlier.
|
||||
* Will be threadlocal/re-used - do not retain references.
|
||||
*/
|
||||
QuadEmitter getEmitter();
|
||||
|
||||
/**
|
||||
* Causes all models/quads/meshes sent to this consumer to be transformed by the provided
|
||||
* {@link QuadTransform} that edits each quad before buffering. Quads in the mesh will
|
||||
* be passed to the {@link QuadTransform} for modification before offsets, face culling or lighting are applied.
|
||||
* Meant for animation and mesh customization.<p>
|
||||
*
|
||||
* You MUST call {@link #popTransform()} after model is done outputting quads.
|
||||
*
|
||||
* More than one transformer can be added to the context. Transformers are applied in reverse order.
|
||||
* (Last pushed is applied first.)<p>
|
||||
*
|
||||
* Meshes are never mutated by the transformer - only buffered quads. This ensures thread-safe
|
||||
* use of meshes/models across multiple chunk builders.<p>
|
||||
*
|
||||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
void pushTransform(QuadTransform transform);
|
||||
|
||||
/**
|
||||
* Removes the transformation added by the last call to {@link #pushTransform(QuadTransform)}.
|
||||
* MUST be called before exiting from {@link FabricBakedModel} .emit... methods.
|
||||
*/
|
||||
void popTransform();
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface QuadTransform {
|
||||
/**
|
||||
* Return false to filter out quads from rendering. When more than one transform
|
||||
* is in effect, returning false means unapplied transforms will not receive the quad.
|
||||
*/
|
||||
boolean transform(MutableQuadView quad);
|
||||
}
|
||||
/**
|
||||
* Used by models to send vertex data previously baked via {@link MeshBuilder}.
|
||||
* The fastest option and preferred whenever feasible.
|
||||
*/
|
||||
Consumer<Mesh> meshConsumer();
|
||||
|
||||
/**
|
||||
* Fabric causes vanilla baked models to send themselves
|
||||
* via this interface. Can also be used by compound models that contain a mix
|
||||
* of vanilla baked models, packaged quads and/or dynamic elements.
|
||||
*/
|
||||
Consumer<BakedModel> fallbackConsumer();
|
||||
|
||||
/**
|
||||
* Returns a {@link QuadEmitter} instance that emits directly to the render buffer.
|
||||
* It remains necessary to call {@link QuadEmitter#emit()} to output the quad.<p>
|
||||
*
|
||||
* This method will always be less performant than passing pre-baked meshes
|
||||
* via {@link #meshConsumer()}. It should be used sparingly for model components that
|
||||
* demand it - text, icons, dynamic indicators, or other elements that vary too
|
||||
* much for static baking to be feasible.<p>
|
||||
*
|
||||
* Calling this method invalidates any {@link QuadEmitter} returned earlier.
|
||||
* Will be threadlocal/re-used - do not retain references.
|
||||
*/
|
||||
QuadEmitter getEmitter();
|
||||
|
||||
/**
|
||||
* Causes all models/quads/meshes sent to this consumer to be transformed by the provided
|
||||
* {@link QuadTransform} that edits each quad before buffering. Quads in the mesh will
|
||||
* be passed to the {@link QuadTransform} for modification before offsets, face culling or lighting are applied.
|
||||
* Meant for animation and mesh customization.<p>
|
||||
*
|
||||
* You MUST call {@link #popTransform()} after model is done outputting quads.
|
||||
*
|
||||
* More than one transformer can be added to the context. Transformers are applied in reverse order.
|
||||
* (Last pushed is applied first.)<p>
|
||||
*
|
||||
* Meshes are never mutated by the transformer - only buffered quads. This ensures thread-safe
|
||||
* use of meshes/models across multiple chunk builders.<p>
|
||||
*
|
||||
* Only the renderer should implement or extend this interface.
|
||||
*/
|
||||
void pushTransform(QuadTransform transform);
|
||||
|
||||
/**
|
||||
* Removes the transformation added by the last call to {@link #pushTransform(QuadTransform)}.
|
||||
* MUST be called before exiting from {@link FabricBakedModel} .emit... methods.
|
||||
*/
|
||||
void popTransform();
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface QuadTransform {
|
||||
/**
|
||||
* Return false to filter out quads from rendering. When more than one transform
|
||||
* is in effect, returning false means unapplied transforms will not receive the quad.
|
||||
*/
|
||||
boolean transform(MutableQuadView quad);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,31 +39,31 @@ import net.minecraft.world.BlockRenderView;
|
|||
* quads that are baked with single-layer, UV-locked damage texture.
|
||||
*/
|
||||
public class DamageModel extends ForwardingBakedModel {
|
||||
static final RenderMaterial DAMAGE_MATERIAL = RendererAccess.INSTANCE.hasRenderer() ? RendererAccess.INSTANCE.getRenderer().materialFinder().find() : null;
|
||||
|
||||
private DamageTransform damageTransform = new DamageTransform();
|
||||
|
||||
public void prepare(BakedModel wrappedModel, Sprite sprite, BlockState blockState, BlockPos blockPos) {
|
||||
this.damageTransform.damageSprite = sprite;
|
||||
this.wrapped = wrappedModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(damageTransform);
|
||||
((FabricBakedModel)wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
|
||||
private static class DamageTransform implements RenderContext.QuadTransform {
|
||||
private Sprite damageSprite;
|
||||
|
||||
@Override
|
||||
public boolean transform(MutableQuadView quad) {
|
||||
quad.material(DAMAGE_MATERIAL);
|
||||
quad.spriteBake(0, damageSprite, MutableQuadView.BAKE_LOCK_UV);
|
||||
quad.colorIndex(-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
static final RenderMaterial DAMAGE_MATERIAL = RendererAccess.INSTANCE.hasRenderer() ? RendererAccess.INSTANCE.getRenderer().materialFinder().find() : null;
|
||||
|
||||
private DamageTransform damageTransform = new DamageTransform();
|
||||
|
||||
public void prepare(BakedModel wrappedModel, Sprite sprite, BlockState blockState, BlockPos blockPos) {
|
||||
this.damageTransform.damageSprite = sprite;
|
||||
this.wrapped = wrappedModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(damageTransform);
|
||||
((FabricBakedModel) wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
|
||||
private static class DamageTransform implements RenderContext.QuadTransform {
|
||||
private Sprite damageSprite;
|
||||
|
||||
@Override
|
||||
public boolean transform(MutableQuadView quad) {
|
||||
quad.material(DAMAGE_MATERIAL);
|
||||
quad.spriteBake(0, damageSprite, MutableQuadView.BAKE_LOCK_UV);
|
||||
quad.colorIndex(-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,36 +19,36 @@ package net.fabricmc.fabric.impl.renderer;
|
|||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
||||
|
||||
public final class RendererAccessImpl implements RendererAccess{
|
||||
public static final RendererAccessImpl INSTANCE = new RendererAccessImpl();
|
||||
public final class RendererAccessImpl implements RendererAccess {
|
||||
public static final RendererAccessImpl INSTANCE = new RendererAccessImpl();
|
||||
|
||||
// private constructor
|
||||
private RendererAccessImpl() { };
|
||||
// private constructor
|
||||
private RendererAccessImpl() { }
|
||||
|
||||
@Override
|
||||
public final void registerRenderer(Renderer renderer) {
|
||||
if(renderer == null) {
|
||||
throw new NullPointerException("Attempt to register a NULL rendering plug-in.");
|
||||
} else if(activeRenderer != null) {
|
||||
throw new UnsupportedOperationException("A second rendering plug-in attempted to register. Multiple rendering plug-ins are not supported.");
|
||||
} else {
|
||||
activeRenderer = renderer;
|
||||
hasActiveRenderer = true;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public final void registerRenderer(Renderer renderer) {
|
||||
if (renderer == null) {
|
||||
throw new NullPointerException("Attempt to register a NULL rendering plug-in.");
|
||||
} else if (activeRenderer != null) {
|
||||
throw new UnsupportedOperationException("A second rendering plug-in attempted to register. Multiple rendering plug-ins are not supported.");
|
||||
} else {
|
||||
activeRenderer = renderer;
|
||||
hasActiveRenderer = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Renderer activeRenderer = null;
|
||||
private Renderer activeRenderer = null;
|
||||
|
||||
/** avoids null test every call to {@link #hasRenderer()} */
|
||||
private boolean hasActiveRenderer = false;
|
||||
/** avoids null test every call to {@link #hasRenderer()} */
|
||||
private boolean hasActiveRenderer = false;
|
||||
|
||||
@Override
|
||||
public final Renderer getRenderer() {
|
||||
return activeRenderer;
|
||||
}
|
||||
@Override
|
||||
public final Renderer getRenderer() {
|
||||
return activeRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean hasRenderer() {
|
||||
return hasActiveRenderer;
|
||||
}
|
||||
@Override
|
||||
public final boolean hasRenderer() {
|
||||
return hasActiveRenderer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,104 +36,112 @@ import net.minecraft.util.Identifier;
|
|||
* a fixed cell size.
|
||||
*/
|
||||
public class SpriteFinderImpl implements SpriteFinder {
|
||||
private final Node root;
|
||||
|
||||
public SpriteFinderImpl(Map<Identifier, Sprite> sprites) {
|
||||
root = new Node(0.5f, 0.5f, 0.25f);
|
||||
sprites.values().forEach(root::add);
|
||||
}
|
||||
private final Node root;
|
||||
|
||||
@Override
|
||||
public Sprite find(QuadView quad, int textureIndex) {
|
||||
float u = 0;
|
||||
float v = 0;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
u += quad.spriteU(i, textureIndex);
|
||||
v += quad.spriteV(i, textureIndex);
|
||||
}
|
||||
return find(u * 0.25f, v * 0.25f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sprite find(float u, float v) {
|
||||
return root.find(u, v);
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
final float midU;
|
||||
final float midV;
|
||||
final float cellRadius;
|
||||
Object lowLow = null;
|
||||
Object lowHigh = null;
|
||||
Object highLow = null;
|
||||
Object highHigh = null;
|
||||
|
||||
Node(float midU, float midV, float radius) {
|
||||
this.midU = midU;
|
||||
this.midV = midV;
|
||||
this.cellRadius = radius;
|
||||
}
|
||||
|
||||
static final float EPS = 0.00001f;
|
||||
|
||||
void add(Sprite sprite) {
|
||||
final boolean lowU = sprite.getMinU() < midU - EPS;
|
||||
final boolean highU = sprite.getMaxU() > midU + EPS;
|
||||
final boolean lowV = sprite.getMinV() < midV - EPS;
|
||||
final boolean highV = sprite.getMaxV() > midV + EPS;
|
||||
if(lowU && lowV) {
|
||||
addInner(sprite, lowLow, -1, -1, q -> lowLow = q);
|
||||
}
|
||||
if(lowU && highV) {
|
||||
addInner(sprite, lowHigh, -1, 1, q -> lowHigh = q);
|
||||
}
|
||||
if(highU && lowV) {
|
||||
addInner(sprite, highLow, 1, -1, q -> highLow = q);
|
||||
}
|
||||
if(highU && highV) {
|
||||
addInner(sprite, highHigh, 1, 1, q -> highHigh = q);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInner(Sprite sprite, Object quadrant, int uStep, int vStep, Consumer<Object> setter) {
|
||||
if(quadrant == null) {
|
||||
setter.accept(sprite);
|
||||
} else if (quadrant instanceof Node) {
|
||||
((Node)quadrant).add(sprite);
|
||||
} else {
|
||||
Node n = new Node(midU + cellRadius * uStep, midV + cellRadius * vStep, cellRadius * 0.5f);
|
||||
if(quadrant instanceof Sprite) {
|
||||
n.add((Sprite)quadrant);
|
||||
}
|
||||
n.add(sprite);
|
||||
setter.accept(n);
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite find(float u, float v) {
|
||||
if(u < midU) {
|
||||
return v < midV ? findInner(lowLow, u, v) : findInner(lowHigh, u, v);
|
||||
} else {
|
||||
return v < midV ? findInner(highLow, u, v) : findInner(highHigh, u, v);
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite findInner(Object quadrant, float u, float v) {
|
||||
if(quadrant instanceof Sprite) {
|
||||
return (Sprite)quadrant;
|
||||
} else if (quadrant instanceof Node) {
|
||||
return ((Node)quadrant).find(u, v);
|
||||
} else {
|
||||
return MissingSprite.getMissingSprite();
|
||||
}
|
||||
}
|
||||
}
|
||||
public SpriteFinderImpl(Map<Identifier, Sprite> sprites) {
|
||||
root = new Node(0.5f, 0.5f, 0.25f);
|
||||
sprites.values().forEach(root::add);
|
||||
}
|
||||
|
||||
public static SpriteFinderImpl get(SpriteAtlasTexture atlas) {
|
||||
return ((SpriteFinderAccess)atlas).fabric_spriteFinder();
|
||||
}
|
||||
|
||||
public static interface SpriteFinderAccess {
|
||||
SpriteFinderImpl fabric_spriteFinder();
|
||||
}
|
||||
@Override
|
||||
public Sprite find(QuadView quad, int textureIndex) {
|
||||
float u = 0;
|
||||
float v = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u += quad.spriteU(i, textureIndex);
|
||||
v += quad.spriteV(i, textureIndex);
|
||||
}
|
||||
|
||||
return find(u * 0.25f, v * 0.25f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sprite find(float u, float v) {
|
||||
return root.find(u, v);
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
final float midU;
|
||||
final float midV;
|
||||
final float cellRadius;
|
||||
Object lowLow = null;
|
||||
Object lowHigh = null;
|
||||
Object highLow = null;
|
||||
Object highHigh = null;
|
||||
|
||||
Node(float midU, float midV, float radius) {
|
||||
this.midU = midU;
|
||||
this.midV = midV;
|
||||
this.cellRadius = radius;
|
||||
}
|
||||
|
||||
static final float EPS = 0.00001f;
|
||||
|
||||
void add(Sprite sprite) {
|
||||
final boolean lowU = sprite.getMinU() < midU - EPS;
|
||||
final boolean highU = sprite.getMaxU() > midU + EPS;
|
||||
final boolean lowV = sprite.getMinV() < midV - EPS;
|
||||
final boolean highV = sprite.getMaxV() > midV + EPS;
|
||||
|
||||
if (lowU && lowV) {
|
||||
addInner(sprite, lowLow, -1, -1, q -> lowLow = q);
|
||||
}
|
||||
|
||||
if (lowU && highV) {
|
||||
addInner(sprite, lowHigh, -1, 1, q -> lowHigh = q);
|
||||
}
|
||||
|
||||
if (highU && lowV) {
|
||||
addInner(sprite, highLow, 1, -1, q -> highLow = q);
|
||||
}
|
||||
|
||||
if (highU && highV) {
|
||||
addInner(sprite, highHigh, 1, 1, q -> highHigh = q);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInner(Sprite sprite, Object quadrant, int uStep, int vStep, Consumer<Object> setter) {
|
||||
if (quadrant == null) {
|
||||
setter.accept(sprite);
|
||||
} else if (quadrant instanceof Node) {
|
||||
((Node) quadrant).add(sprite);
|
||||
} else {
|
||||
Node n = new Node(midU + cellRadius * uStep, midV + cellRadius * vStep, cellRadius * 0.5f);
|
||||
|
||||
if (quadrant instanceof Sprite) {
|
||||
n.add((Sprite) quadrant);
|
||||
}
|
||||
|
||||
n.add(sprite);
|
||||
setter.accept(n);
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite find(float u, float v) {
|
||||
if (u < midU) {
|
||||
return v < midV ? findInner(lowLow, u, v) : findInner(lowHigh, u, v);
|
||||
} else {
|
||||
return v < midV ? findInner(highLow, u, v) : findInner(highHigh, u, v);
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite findInner(Object quadrant, float u, float v) {
|
||||
if (quadrant instanceof Sprite) {
|
||||
return (Sprite) quadrant;
|
||||
} else if (quadrant instanceof Node) {
|
||||
return ((Node) quadrant).find(u, v);
|
||||
} else {
|
||||
return MissingSprite.getMissingSprite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SpriteFinderImpl get(SpriteAtlasTexture atlas) {
|
||||
return ((SpriteFinderAccess) atlas).fabric_spriteFinder();
|
||||
}
|
||||
|
||||
public static interface SpriteFinderAccess {
|
||||
SpriteFinderImpl fabric_spriteFinder();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,18 +34,18 @@ import net.minecraft.util.math.BlockPos;
|
|||
*/
|
||||
@Mixin(BakedModel.class)
|
||||
public interface MixinBakedModel extends FabricBakedModel {
|
||||
@Override
|
||||
public default boolean isVanillaAdapter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.fallbackConsumer().accept((BakedModel)this);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.fallbackConsumer().accept((BakedModel)this);
|
||||
}
|
||||
@Override
|
||||
public default boolean isVanillaAdapter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.fallbackConsumer().accept((BakedModel) this);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.fallbackConsumer().accept((BakedModel) this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,33 +42,35 @@ import net.minecraft.world.BlockRenderView;
|
|||
*/
|
||||
@Mixin(BlockRenderManager.class)
|
||||
public abstract class MixinBlockRenderManager {
|
||||
@Shadow private BlockModelRenderer renderer;
|
||||
@Shadow private Random random;
|
||||
|
||||
private static final ThreadLocal<MutablePair<DamageModel, BakedModel>> DAMAGE_STATE = ThreadLocal.withInitial(() -> MutablePair.of(new DamageModel(), null));
|
||||
|
||||
/**
|
||||
* Intercept the model assignment from getModel() - simpler than capturing entire LVT.
|
||||
*/
|
||||
@ModifyVariable(method = "tesselateDamage", at = @At(value = "STORE", ordinal = 0), allow = 1, require = 1)
|
||||
private BakedModel hookTesselateDamageModel(BakedModel modelIn) {
|
||||
DAMAGE_STATE.get().right = modelIn;
|
||||
return modelIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the model we just captured is a fabric model, render it using a specialized
|
||||
* damage render context and cancel rest of the logic. Avoids creating a bunch of
|
||||
* vanilla quads for complex meshes and honors dynamic model geometry.
|
||||
*/
|
||||
@Inject(method = "tesselateDamage", cancellable = true,
|
||||
at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private void hookTesselateDamage(BufferBuilder bufferBuilder, BlockState blockState, BlockPos blockPos, Sprite sprite, BlockRenderView blockView, CallbackInfo ci) {
|
||||
MutablePair<DamageModel, BakedModel> damageState = DAMAGE_STATE.get();
|
||||
if(damageState.right != null && !((FabricBakedModel)damageState.right).isVanillaAdapter()) {
|
||||
damageState.left.prepare(damageState.right, sprite, blockState, blockPos);
|
||||
this.renderer.tesselate(blockView, damageState.left, blockState, blockPos, bufferBuilder, true, this.random, blockState.getRenderingSeed(blockPos));
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
@Shadow
|
||||
private BlockModelRenderer renderer;
|
||||
@Shadow
|
||||
private Random random;
|
||||
|
||||
private static final ThreadLocal<MutablePair<DamageModel, BakedModel>> DAMAGE_STATE = ThreadLocal.withInitial(() -> MutablePair.of(new DamageModel(), null));
|
||||
|
||||
/**
|
||||
* Intercept the model assignment from getModel() - simpler than capturing entire LVT.
|
||||
*/
|
||||
@ModifyVariable(method = "tesselateDamage", at = @At(value = "STORE", ordinal = 0), allow = 1, require = 1)
|
||||
private BakedModel hookTesselateDamageModel(BakedModel modelIn) {
|
||||
DAMAGE_STATE.get().right = modelIn;
|
||||
return modelIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the model we just captured is a fabric model, render it using a specialized
|
||||
* damage render context and cancel rest of the logic. Avoids creating a bunch of
|
||||
* vanilla quads for complex meshes and honors dynamic model geometry.
|
||||
*/
|
||||
@Inject(method = "tesselateDamage", cancellable = true, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private void hookTesselateDamage(BufferBuilder bufferBuilder, BlockState blockState, BlockPos blockPos, Sprite sprite, BlockRenderView blockView, CallbackInfo ci) {
|
||||
MutablePair<DamageModel, BakedModel> damageState = DAMAGE_STATE.get();
|
||||
|
||||
if (damageState.right != null && !((FabricBakedModel) damageState.right).isVanillaAdapter()) {
|
||||
damageState.left.prepare(damageState.right, sprite, blockState, blockPos);
|
||||
this.renderer.tesselate(blockView, damageState.left, blockState, blockPos, bufferBuilder, true, this.random, blockState.getRenderingSeed(blockPos));
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,22 +31,24 @@ import net.minecraft.util.Identifier;
|
|||
|
||||
@Mixin(SpriteAtlasTexture.class)
|
||||
public class MixinSpriteAtlasTexture implements SpriteFinderImpl.SpriteFinderAccess {
|
||||
@Shadow private Map<Identifier, Sprite> sprites;
|
||||
|
||||
private SpriteFinderImpl fabric_spriteFinder = null;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "upload")
|
||||
private void uploadHook(SpriteAtlasTexture.Data input, CallbackInfo info) {
|
||||
fabric_spriteFinder = null;
|
||||
}
|
||||
@Shadow
|
||||
private Map<Identifier, Sprite> sprites;
|
||||
|
||||
@Override
|
||||
public SpriteFinderImpl fabric_spriteFinder() {
|
||||
SpriteFinderImpl result = fabric_spriteFinder;
|
||||
if(result == null) {
|
||||
result = new SpriteFinderImpl(sprites);
|
||||
fabric_spriteFinder = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private SpriteFinderImpl fabric_spriteFinder = null;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "upload")
|
||||
private void uploadHook(SpriteAtlasTexture.Data input, CallbackInfo info) {
|
||||
fabric_spriteFinder = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteFinderImpl fabric_spriteFinder() {
|
||||
SpriteFinderImpl result = fabric_spriteFinder;
|
||||
|
||||
if (result == null) {
|
||||
result = new SpriteFinderImpl(sprites);
|
||||
fabric_spriteFinder = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "fabric-renderer-indigo"
|
||||
version = getSubprojectVersion(project, "0.2.0")
|
||||
version = getSubprojectVersion(project, "0.2.1")
|
||||
|
||||
dependencies {
|
||||
compile project(path: ':fabric-api-base', configuration: 'dev')
|
||||
|
|
|
@ -41,22 +41,22 @@ public class Indigo implements ClientModInitializer {
|
|||
public static final boolean FIX_SMOOTH_LIGHTING_OFFSET;
|
||||
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
|
||||
public static final boolean FIX_LUMINOUS_AO_SHADE;
|
||||
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static boolean asBoolean(String property, boolean defValue) {
|
||||
switch (asTriState(property)) {
|
||||
case TRUE:
|
||||
return true;
|
||||
case FALSE:
|
||||
return false;
|
||||
default:
|
||||
return defValue;
|
||||
case TRUE:
|
||||
return true;
|
||||
case FALSE:
|
||||
return false;
|
||||
default:
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static <T extends Enum> T asEnum(String property, T defValue) {
|
||||
private static <T extends Enum> T asEnum(String property, T defValue) {
|
||||
if (property == null || property.isEmpty()) {
|
||||
return defValue;
|
||||
} else {
|
||||
|
@ -76,13 +76,13 @@ public class Indigo implements ClientModInitializer {
|
|||
return TriState.DEFAULT;
|
||||
} else {
|
||||
switch (property.toLowerCase(Locale.ROOT)) {
|
||||
case "true":
|
||||
return TriState.TRUE;
|
||||
case "false":
|
||||
return TriState.FALSE;
|
||||
case "auto":
|
||||
default:
|
||||
return TriState.DEFAULT;
|
||||
case "true":
|
||||
return TriState.TRUE;
|
||||
case "false":
|
||||
return TriState.FALSE;
|
||||
case "auto":
|
||||
default:
|
||||
return TriState.DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ public class Indigo implements ClientModInitializer {
|
|||
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
|
||||
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
|
||||
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false);
|
||||
|
||||
|
||||
try (FileOutputStream stream = new FileOutputStream(configFile)) {
|
||||
properties.store(stream, "Indigo properties file");
|
||||
} catch (IOException e) {
|
||||
|
@ -122,16 +122,16 @@ public class Indigo implements ClientModInitializer {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
|
||||
LOGGER.info("[Indigo] Registering Indigo renderer!");
|
||||
if(IndigoMixinConfigPlugin.shouldForceCompatibility()) {
|
||||
LOGGER.info("[Indigo] Compatibility mode enabled.");
|
||||
}
|
||||
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
|
||||
} else {
|
||||
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
|
||||
LOGGER.info("[Indigo] Registering Indigo renderer!");
|
||||
if (IndigoMixinConfigPlugin.shouldForceCompatibility()) {
|
||||
LOGGER.info("[Indigo] Compatibility mode enabled.");
|
||||
}
|
||||
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
|
||||
} else {
|
||||
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,39 +28,43 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
public class IndigoMixinConfigPlugin implements IMixinConfigPlugin {
|
||||
/** Set by other renderers to disable loading of Indigo */
|
||||
/** Set by other renderers to disable loading of Indigo */
|
||||
private static final String JSON_KEY_DISABLE_INDIGO = "fabric-renderer-api-v1:contains_renderer";
|
||||
/** Disables vanilla block tesselation and ensures vertex format compatibility */
|
||||
/**
|
||||
* Disables vanilla block tesselation and ensures vertex format compatibility
|
||||
*/
|
||||
private static final String JSON_KEY_FORCE_COMPATIBILITY = "fabric-renderer-indigo:force_compatibility";
|
||||
|
||||
|
||||
private static boolean needsLoad = true;
|
||||
|
||||
|
||||
private static boolean indigoApplicable = true;
|
||||
private static boolean forceCompatibility = false;
|
||||
|
||||
private static void loadIfNeeded() {
|
||||
if(needsLoad) {
|
||||
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
|
||||
final ModMetadata meta = container.getMetadata();
|
||||
if (meta.containsCustomValue(JSON_KEY_DISABLE_INDIGO)) {
|
||||
indigoApplicable = false;
|
||||
} else if (meta.containsCustomValue(JSON_KEY_FORCE_COMPATIBILITY)) {
|
||||
forceCompatibility = true;
|
||||
}
|
||||
}
|
||||
needsLoad = false;
|
||||
}
|
||||
if (needsLoad) {
|
||||
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
|
||||
final ModMetadata meta = container.getMetadata();
|
||||
|
||||
if (meta.containsCustomValue(JSON_KEY_DISABLE_INDIGO)) {
|
||||
indigoApplicable = false;
|
||||
} else if (meta.containsCustomValue(JSON_KEY_FORCE_COMPATIBILITY)) {
|
||||
forceCompatibility = true;
|
||||
}
|
||||
}
|
||||
needsLoad = false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean shouldApplyIndigo() {
|
||||
loadIfNeeded();
|
||||
loadIfNeeded();
|
||||
return indigoApplicable;
|
||||
}
|
||||
|
||||
static boolean shouldForceCompatibility() {
|
||||
loadIfNeeded();
|
||||
return forceCompatibility;
|
||||
}
|
||||
|
||||
static boolean shouldForceCompatibility() {
|
||||
loadIfNeeded();
|
||||
return forceCompatibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
|
||||
|
|
|
@ -31,39 +31,40 @@ import net.minecraft.util.Identifier;
|
|||
* features defined in the API except shaders and offers no special materials.
|
||||
*/
|
||||
public class IndigoRenderer implements Renderer {
|
||||
public static final IndigoRenderer INSTANCE = new IndigoRenderer();
|
||||
|
||||
public static final RenderMaterialImpl.Value MATERIAL_STANDARD = (Value) INSTANCE.materialFinder().find();
|
||||
|
||||
static {
|
||||
INSTANCE.registerMaterial(RenderMaterial.MATERIAL_STANDARD, MATERIAL_STANDARD);
|
||||
}
|
||||
|
||||
private final HashMap<Identifier, RenderMaterial> materialMap = new HashMap<>();
|
||||
|
||||
private IndigoRenderer() { };
|
||||
public static final IndigoRenderer INSTANCE = new IndigoRenderer();
|
||||
|
||||
@Override
|
||||
public MeshBuilder meshBuilder() {
|
||||
return new MeshBuilderImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder materialFinder() {
|
||||
return new RenderMaterialImpl.Finder();
|
||||
}
|
||||
public static final RenderMaterialImpl.Value MATERIAL_STANDARD = (Value) INSTANCE.materialFinder().find();
|
||||
|
||||
@Override
|
||||
public RenderMaterial materialById(Identifier id) {
|
||||
return materialMap.get(id);
|
||||
}
|
||||
static {
|
||||
INSTANCE.registerMaterial(RenderMaterial.MATERIAL_STANDARD, MATERIAL_STANDARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerMaterial(Identifier id, RenderMaterial material) {
|
||||
if(materialMap.containsKey(id))
|
||||
return false;
|
||||
// cast to prevent acceptance of impostor implementations
|
||||
materialMap.put(id, material);
|
||||
return true;
|
||||
}
|
||||
private final HashMap<Identifier, RenderMaterial> materialMap = new HashMap<>();
|
||||
|
||||
private IndigoRenderer() {
|
||||
};
|
||||
|
||||
@Override
|
||||
public MeshBuilder meshBuilder() {
|
||||
return new MeshBuilderImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder materialFinder() {
|
||||
return new RenderMaterialImpl.Finder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderMaterial materialById(Identifier id) {
|
||||
return materialMap.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerMaterial(Identifier id, RenderMaterial material) {
|
||||
if (materialMap.containsKey(id)) return false;
|
||||
|
||||
// cast to prevent acceptance of impostor implementations
|
||||
materialMap.put(id, material);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
package net.fabricmc.indigo.renderer;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
|
||||
/**
|
||||
* Default implementation of the standard render materials.
|
||||
|
@ -29,153 +30,113 @@ import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
|||
* easy/fast interning via int/object hashmap.
|
||||
*/
|
||||
public abstract class RenderMaterialImpl {
|
||||
private static final BlendMode[] BLEND_MODES = BlendMode.values();
|
||||
|
||||
/**
|
||||
* Indigo currently support up to 3 sprite layers but is configured to recognize only one.
|
||||
*/
|
||||
public static final int MAX_SPRITE_DEPTH = 1;
|
||||
|
||||
private static final int TEXTURE_DEPTH_MASK = 3;
|
||||
private static final int TEXTURE_DEPTH_SHIFT = 0;
|
||||
|
||||
private static final int BLEND_MODE_MASK = 7;
|
||||
private static final int[] BLEND_MODE_SHIFT = new int[3];
|
||||
private static final int[] COLOR_DISABLE_FLAGS = new int[3];
|
||||
private static final int[] EMISSIVE_FLAGS = new int[3];
|
||||
private static final int[] DIFFUSE_FLAGS = new int[3];
|
||||
private static final int[] AO_FLAGS = new int[3];
|
||||
|
||||
static {
|
||||
int shift = Integer.bitCount(TEXTURE_DEPTH_MASK);
|
||||
for(int i = 0; i < 3; i++) {
|
||||
BLEND_MODE_SHIFT[i] = shift;
|
||||
shift += Integer.bitCount(BLEND_MODE_MASK);
|
||||
COLOR_DISABLE_FLAGS[i] = 1 << shift++;
|
||||
EMISSIVE_FLAGS[i] = 1 << shift++;
|
||||
DIFFUSE_FLAGS[i] = 1 << shift++;
|
||||
AO_FLAGS[i] = 1 << shift++;
|
||||
}
|
||||
}
|
||||
|
||||
static private final ObjectArrayList<Value> LIST = new ObjectArrayList<>();
|
||||
static private final Int2ObjectOpenHashMap<Value> MAP = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public static RenderMaterialImpl.Value byIndex(int index) {
|
||||
return LIST.get(index);
|
||||
}
|
||||
|
||||
protected int bits;
|
||||
|
||||
public BlendMode blendMode(int textureIndex) {
|
||||
return BLEND_MODES[(bits >> BLEND_MODE_SHIFT[textureIndex]) & BLEND_MODE_MASK];
|
||||
}
|
||||
|
||||
public boolean disableColorIndex(int textureIndex) {
|
||||
return (bits & COLOR_DISABLE_FLAGS[textureIndex]) != 0;
|
||||
}
|
||||
|
||||
public int spriteDepth() {
|
||||
return 1 + ((bits >> TEXTURE_DEPTH_SHIFT) & TEXTURE_DEPTH_MASK);
|
||||
}
|
||||
|
||||
public boolean emissive(int textureIndex) {
|
||||
return (bits & EMISSIVE_FLAGS[textureIndex]) != 0;
|
||||
}
|
||||
|
||||
public boolean disableDiffuse(int textureIndex) {
|
||||
return (bits & DIFFUSE_FLAGS[textureIndex]) != 0;
|
||||
}
|
||||
private static final BlendMode[] BLEND_MODES = BlendMode.values();
|
||||
|
||||
public boolean disableAo(int textureIndex) {
|
||||
return (bits & AO_FLAGS[textureIndex]) != 0;
|
||||
}
|
||||
|
||||
public static class Value extends RenderMaterialImpl implements RenderMaterial {
|
||||
private final int index;
|
||||
|
||||
/** True if any texture wants AO shading. Simplifies check made by renderer at buffer-time. */
|
||||
public final boolean hasAo;
|
||||
|
||||
/** True if any texture wants emissive lighting. Simplifies check made by renderer at buffer-time. */
|
||||
public final boolean hasEmissive;
|
||||
|
||||
private Value(int index, int bits) {
|
||||
this.index = index;
|
||||
this.bits = bits;
|
||||
hasAo = !disableAo(0)
|
||||
|| (spriteDepth() > 1 && !disableAo(1))
|
||||
|| (spriteDepth() == 3 && !disableAo(2));
|
||||
hasEmissive = emissive(0) || emissive(1) || emissive(2);
|
||||
}
|
||||
|
||||
public int index() {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Finder extends RenderMaterialImpl implements MaterialFinder {
|
||||
@Override
|
||||
public synchronized RenderMaterial find() {
|
||||
Value result = MAP.get(bits);
|
||||
if(result == null) {
|
||||
result = new Value(LIST.size(), bits);
|
||||
LIST.add(result);
|
||||
MAP.put(bits, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private static final int BLEND_MODE_MASK = MathHelper.smallestEncompassingPowerOfTwo(BlendMode.values().length) - 1;
|
||||
private static final int COLOR_DISABLE_FLAG = BLEND_MODE_MASK + 1;
|
||||
private static final int EMISSIVE_FLAG = COLOR_DISABLE_FLAG << 1;
|
||||
private static final int DIFFUSE_FLAG = EMISSIVE_FLAG << 1;
|
||||
private static final int AO_FLAG = DIFFUSE_FLAG << 1;
|
||||
public static final int VALUE_COUNT = (AO_FLAG << 1);
|
||||
|
||||
@Override
|
||||
public MaterialFinder clear() {
|
||||
bits = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder blendMode(int textureIndex, BlendMode blendMode) {
|
||||
if (blendMode == null) blendMode = BlendMode.DEFAULT;
|
||||
final int shift = BLEND_MODE_SHIFT[textureIndex];
|
||||
// zero position is null (default) value
|
||||
bits = (bits & ~(BLEND_MODE_MASK << shift)) | (blendMode.ordinal() << shift);
|
||||
return this;
|
||||
}
|
||||
static private final Value[] VALUES = new Value[VALUE_COUNT];
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableColorIndex(int textureIndex, boolean disable) {
|
||||
final int flag = COLOR_DISABLE_FLAGS[textureIndex];
|
||||
bits = disable ? (bits | flag) : (bits & ~flag);
|
||||
return this;
|
||||
}
|
||||
static {
|
||||
for (int i = 0; i < VALUE_COUNT; i++) {
|
||||
VALUES[i] = new Value(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder spriteDepth(int depth) {
|
||||
if(depth < 1 || depth > MAX_SPRITE_DEPTH) {
|
||||
throw new IndexOutOfBoundsException("Invalid sprite depth: " + depth);
|
||||
}
|
||||
bits = (bits & ~(TEXTURE_DEPTH_MASK << TEXTURE_DEPTH_SHIFT)) | (--depth << TEXTURE_DEPTH_SHIFT);
|
||||
return this;
|
||||
}
|
||||
public static RenderMaterialImpl.Value byIndex(int index) {
|
||||
return VALUES[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder emissive(int textureIndex, boolean isEmissive) {
|
||||
final int flag = EMISSIVE_FLAGS[textureIndex];
|
||||
bits = isEmissive ? (bits | flag) : (bits & ~flag);
|
||||
return this;
|
||||
}
|
||||
protected int bits;
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableDiffuse(int textureIndex, boolean disable) {
|
||||
final int flag = DIFFUSE_FLAGS[textureIndex];
|
||||
bits = disable ? (bits | flag) : (bits & ~flag);
|
||||
return this;
|
||||
}
|
||||
public BlendMode blendMode(int textureIndex) {
|
||||
return BLEND_MODES[bits & BLEND_MODE_MASK];
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableAo(int textureIndex, boolean disable) {
|
||||
final int flag = AO_FLAGS[textureIndex];
|
||||
bits = disable ? (bits | flag) : (bits & ~flag);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
public boolean disableColorIndex(int textureIndex) {
|
||||
return (bits & COLOR_DISABLE_FLAG) != 0;
|
||||
}
|
||||
|
||||
public int spriteDepth() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public boolean emissive(int textureIndex) {
|
||||
return (bits & EMISSIVE_FLAG) != 0;
|
||||
}
|
||||
|
||||
public boolean disableDiffuse(int textureIndex) {
|
||||
return (bits & DIFFUSE_FLAG) != 0;
|
||||
}
|
||||
|
||||
public boolean disableAo(int textureIndex) {
|
||||
return (bits & AO_FLAG) != 0;
|
||||
}
|
||||
|
||||
public static class Value extends RenderMaterialImpl implements RenderMaterial {
|
||||
private Value(int bits) {
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
public int index() {
|
||||
return bits;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Finder extends RenderMaterialImpl implements MaterialFinder {
|
||||
@Override
|
||||
public RenderMaterial find() {
|
||||
return VALUES[bits];
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder clear() {
|
||||
bits = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder blendMode(int textureIndex, BlendMode blendMode) {
|
||||
if (blendMode == null) {
|
||||
blendMode = BlendMode.DEFAULT;
|
||||
}
|
||||
bits = (bits & ~BLEND_MODE_MASK) | blendMode.ordinal();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableColorIndex(int textureIndex, boolean disable) {
|
||||
bits = disable ? (bits | COLOR_DISABLE_FLAG) : (bits & ~COLOR_DISABLE_FLAG);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder spriteDepth(int depth) {
|
||||
Preconditions.checkArgument(depth == 1, "Unsupported sprite depth: %s", depth);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder emissive(int textureIndex, boolean isEmissive) {
|
||||
bits = isEmissive ? (bits | EMISSIVE_FLAG) : (bits & ~EMISSIVE_FLAG);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableDiffuse(int textureIndex, boolean disable) {
|
||||
bits = disable ? (bits | DIFFUSE_FLAG) : (bits & ~DIFFUSE_FLAG);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialFinder disableAo(int textureIndex, boolean disable) {
|
||||
bits = disable ? (bits | AO_FLAG) : (bits & ~AO_FLAG);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.accessor;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
public interface AccessAmbientOcclusionCalculator {
|
||||
float[] fabric_colorMultiplier();
|
||||
|
||||
int[] fabric_brightness();
|
||||
|
||||
void fabric_apply(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, Direction face, float[] aoData, BitSet controlBits);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.accessor;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
public interface AccessBlockModelRenderer {
|
||||
void fabric_updateShape(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits);
|
||||
}
|
|
@ -19,5 +19,5 @@ package net.fabricmc.indigo.renderer.accessor;
|
|||
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
||||
|
||||
public interface AccessBufferBuilder {
|
||||
void fabric_putQuad(QuadViewImpl quad);
|
||||
void fabric_putQuad(QuadViewImpl quad);
|
||||
}
|
||||
|
|
|
@ -20,5 +20,5 @@ import net.minecraft.client.render.BufferBuilder;
|
|||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
public interface AccessChunkRenderer {
|
||||
void fabric_beginBufferBuilding(BufferBuilder bufferBuilder_1, BlockPos blockPos_1);
|
||||
void fabric_beginBufferBuilding(BufferBuilder bufferBuilder_1, BlockPos blockPos_1);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import net.fabricmc.indigo.renderer.render.TerrainRenderContext;
|
|||
* chunk rebuild, thus avoiding repeated thread-local lookups.
|
||||
*/
|
||||
public interface AccessChunkRendererRegion {
|
||||
TerrainRenderContext fabric_getRenderer();
|
||||
void fabric_setRenderer(TerrainRenderContext renderer);
|
||||
TerrainRenderContext fabric_getRenderer();
|
||||
|
||||
void fabric_setRenderer(TerrainRenderContext renderer);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import static net.minecraft.util.math.Direction.SOUTH;
|
|||
import static net.minecraft.util.math.Direction.UP;
|
||||
import static net.minecraft.util.math.Direction.WEST;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
@ -35,7 +36,9 @@ import org.apache.logging.log4j.Logger;
|
|||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessAmbientOcclusionCalculator;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoFace.WeightFunction;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
||||
import net.fabricmc.indigo.renderer.render.BlockRenderInfo;
|
||||
|
@ -52,437 +55,464 @@ import net.minecraft.world.BlockRenderView;
|
|||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class AoCalculator {
|
||||
/** Used to receive a method reference in constructor for ao value lookup. */
|
||||
@FunctionalInterface
|
||||
public static interface AoFunc {
|
||||
float apply(BlockPos pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla models with cubic quads have vertices in a certain order, which allows
|
||||
* us to map them using a lookup. Adapted from enum in vanilla AoCalculator.
|
||||
*/
|
||||
private static final int[][] VERTEX_MAP = new int[6][4];
|
||||
static {
|
||||
VERTEX_MAP[DOWN.getId()] = new int[] {0, 1, 2, 3};
|
||||
VERTEX_MAP[UP.getId()] = new int[] {2, 3, 0, 1};
|
||||
VERTEX_MAP[NORTH.getId()] = new int[] {3, 0, 1, 2};
|
||||
VERTEX_MAP[SOUTH.getId()] = new int[] {0, 1, 2, 3};
|
||||
VERTEX_MAP[WEST.getId()] = new int[] {3, 0, 1, 2};
|
||||
VERTEX_MAP[EAST.getId()] = new int[] {1, 2, 3, 0};
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
/** Used to receive a method reference in constructor for ao value lookup. */
|
||||
@FunctionalInterface
|
||||
public static interface AoFunc {
|
||||
float apply(BlockPos pos);
|
||||
}
|
||||
|
||||
private final VanillaAoCalc vanillaCalc;
|
||||
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
|
||||
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
|
||||
private final BlockRenderInfo blockInfo;
|
||||
private final ToIntFunction<BlockPos> brightnessFunc;
|
||||
private final AoFunc aoFunc;
|
||||
|
||||
/** caches results of {@link #computeFace(Direction, boolean)} for the current block */
|
||||
private final AoFaceData[] faceData = new AoFaceData[12];
|
||||
|
||||
/** indicates which elements of {@link #faceData} have been computed for the current block*/
|
||||
private int completionFlags = 0;
|
||||
|
||||
/** holds per-corner weights - used locally to avoid new allocation */
|
||||
private final float[] w = new float[4];
|
||||
|
||||
// outputs
|
||||
public final float[] ao = new float[4];
|
||||
public final int[] light = new int[4];
|
||||
|
||||
public AoCalculator(BlockRenderInfo blockInfo, ToIntFunction<BlockPos> brightnessFunc, AoFunc aoFunc) {
|
||||
this.blockInfo = blockInfo;
|
||||
this.brightnessFunc = brightnessFunc;
|
||||
this.aoFunc = aoFunc;
|
||||
this.vanillaCalc = new VanillaAoCalc(brightnessFunc, aoFunc);
|
||||
for(int i = 0; i < 12; i++) {
|
||||
faceData[i] = new AoFaceData();
|
||||
}
|
||||
}
|
||||
|
||||
/** call at start of each new block */
|
||||
public void clear() {
|
||||
completionFlags = 0;
|
||||
}
|
||||
|
||||
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
|
||||
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
|
||||
final boolean shouldCompare;
|
||||
|
||||
switch(config) {
|
||||
case VANILLA:
|
||||
calcVanilla(quad);
|
||||
// no point in comparing vanilla with itself
|
||||
shouldCompare = false;
|
||||
break;
|
||||
|
||||
case EMULATE:
|
||||
calcFastVanilla(quad);
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
|
||||
break;
|
||||
|
||||
default:
|
||||
case HYBRID:
|
||||
if(isVanilla) {
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
calcFastVanilla(quad);
|
||||
} else {
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENHANCED:
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
|
||||
if (shouldCompare) {
|
||||
float[] vanillaAo = new float[4];
|
||||
int[] vanillaLight = new int[4];
|
||||
|
||||
vanillaCalc.compute(blockInfo, quad, vanillaAo, vanillaLight);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (light[i] != vanillaLight[i] || !MathHelper.approximatelyEquals(ao[i], vanillaAo[i])) {
|
||||
LOGGER.info(String.format("Mismatch for %s @ %s", blockInfo.blockState.toString(), blockInfo.blockPos.toString()));
|
||||
LOGGER.info(String.format("Flags = %d, LightFace = %s", quad.geometryFlags(), quad.lightFace().toString()));
|
||||
LOGGER.info(String.format(" Old Multiplier: %.2f, %.2f, %.2f, %.2f",
|
||||
vanillaAo[0], vanillaAo[1], vanillaAo[2],vanillaAo[3]));
|
||||
LOGGER.info(String.format(" New Multiplier: %.2f, %.2f, %.2f, %.2f", ao[0], ao[1], ao[2], ao[3]));
|
||||
LOGGER.info(String.format(" Old Brightness: %s, %s, %s, %s",
|
||||
Integer.toHexString(vanillaLight[0]), Integer.toHexString(vanillaLight[1]),
|
||||
Integer.toHexString(vanillaLight[2]), Integer.toHexString(vanillaLight[3])));
|
||||
LOGGER.info(String.format(" New Brightness: %s, %s, %s, %s", Integer.toHexString(light[0]),
|
||||
Integer.toHexString(light[1]), Integer.toHexString(light[2]), Integer.toHexString(light[3])));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Vanilla models with cubic quads have vertices in a certain order, which allows
|
||||
* us to map them using a lookup. Adapted from enum in vanilla AoCalculator.
|
||||
*/
|
||||
private static final int[][] VERTEX_MAP = new int[6][4];
|
||||
static {
|
||||
VERTEX_MAP[DOWN.getId()] = new int[] { 0, 1, 2, 3 };
|
||||
VERTEX_MAP[UP.getId()] = new int[] { 2, 3, 0, 1 };
|
||||
VERTEX_MAP[NORTH.getId()] = new int[] { 3, 0, 1, 2 };
|
||||
VERTEX_MAP[SOUTH.getId()] = new int[] { 0, 1, 2, 3 };
|
||||
VERTEX_MAP[WEST.getId()] = new int[] { 3, 0, 1, 2 };
|
||||
VERTEX_MAP[EAST.getId()] = new int[] { 1, 2, 3, 0 };
|
||||
}
|
||||
|
||||
private void calcVanilla(MutableQuadViewImpl quad) {
|
||||
vanillaCalc.compute(blockInfo, quad, ao, light);
|
||||
}
|
||||
|
||||
private void calcFastVanilla(MutableQuadViewImpl quad) {
|
||||
int flags = quad.geometryFlags();
|
||||
|
||||
// force to block face if shape is full cube - matches vanilla logic
|
||||
if((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
|
||||
flags |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
if((flags & CUBIC_FLAG) == 0) {
|
||||
private final AccessAmbientOcclusionCalculator vanillaCalc;
|
||||
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
|
||||
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
|
||||
private final BlockRenderInfo blockInfo;
|
||||
private final ToIntFunction<BlockPos> brightnessFunc;
|
||||
private final AoFunc aoFunc;
|
||||
|
||||
/** caches results of {@link #computeFace(Direction, boolean)} for the current block */
|
||||
private final AoFaceData[] faceData = new AoFaceData[12];
|
||||
|
||||
/** indicates which elements of {@link #faceData} have been computed for the current block*/
|
||||
private int completionFlags = 0;
|
||||
|
||||
/** holds per-corner weights - used locally to avoid new allocation */
|
||||
private final float[] w = new float[4];
|
||||
|
||||
// outputs
|
||||
public final float[] ao = new float[4];
|
||||
public final int[] light = new int[4];
|
||||
|
||||
public AoCalculator(BlockRenderInfo blockInfo, ToIntFunction<BlockPos> brightnessFunc, AoFunc aoFunc) {
|
||||
this.blockInfo = blockInfo;
|
||||
this.brightnessFunc = brightnessFunc;
|
||||
this.aoFunc = aoFunc;
|
||||
this.vanillaCalc = VanillaAoHelper.get();
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
faceData[i] = new AoFaceData();
|
||||
}
|
||||
}
|
||||
|
||||
/** call at start of each new block */
|
||||
public void clear() {
|
||||
completionFlags = 0;
|
||||
}
|
||||
|
||||
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
|
||||
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
|
||||
final boolean shouldCompare;
|
||||
|
||||
switch (config) {
|
||||
case VANILLA:
|
||||
// prevent NPE in error case of failed reflection for vanilla calculator access
|
||||
if (vanillaCalc == null) {
|
||||
calcFastVanilla(quad);
|
||||
} else {
|
||||
calcVanilla(quad);
|
||||
}
|
||||
|
||||
// no point in comparing vanilla with itself
|
||||
shouldCompare = false;
|
||||
break;
|
||||
|
||||
case EMULATE:
|
||||
calcFastVanilla(quad);
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
|
||||
break;
|
||||
|
||||
default:
|
||||
case HYBRID:
|
||||
if (isVanilla) {
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
calcFastVanilla(quad);
|
||||
} else {
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENHANCED:
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
|
||||
if (shouldCompare && vanillaCalc != null) {
|
||||
float[] vanillaAo = new float[4];
|
||||
int[] vanillaLight = new int[4];
|
||||
calcVanilla(quad, vanillaAo, vanillaLight);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (light[i] != vanillaLight[i] || !MathHelper.approximatelyEquals(ao[i], vanillaAo[i])) {
|
||||
LOGGER.info(String.format("Mismatch for %s @ %s", blockInfo.blockState.toString(), blockInfo.blockPos.toString()));
|
||||
LOGGER.info(String.format("Flags = %d, LightFace = %s", quad.geometryFlags(), quad.lightFace().toString()));
|
||||
LOGGER.info(String.format(" Old Multiplier: %.2f, %.2f, %.2f, %.2f", vanillaAo[0], vanillaAo[1], vanillaAo[2], vanillaAo[3]));
|
||||
LOGGER.info(String.format(" New Multiplier: %.2f, %.2f, %.2f, %.2f", ao[0], ao[1], ao[2], ao[3]));
|
||||
LOGGER.info(String.format(" Old Brightness: %s, %s, %s, %s", Integer.toHexString(vanillaLight[0]), Integer.toHexString(vanillaLight[1]), Integer.toHexString(vanillaLight[2]), Integer.toHexString(vanillaLight[3])));
|
||||
LOGGER.info(String.format(" New Brightness: %s, %s, %s, %s", Integer.toHexString(light[0]), Integer.toHexString(light[1]), Integer.toHexString(light[2]), Integer.toHexString(light[3])));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void calcVanilla(MutableQuadViewImpl quad) {
|
||||
calcVanilla(quad, ao, light);
|
||||
}
|
||||
|
||||
// These are what vanilla AO calc wants, per its usage in vanilla code
|
||||
// Because this instance is effectively thread-local, we preserve instances
|
||||
// to avoid making a new allocation each call.
|
||||
private final float[] vanillaAoData = new float[Direction.values().length * 2];
|
||||
private final BitSet vanillaAoControlBits = new BitSet(3);
|
||||
private final int[] vertexData = new int[EncodingFormat.QUAD_STRIDE];
|
||||
|
||||
private void calcVanilla(MutableQuadViewImpl quad, float[] aoDest, int[] lightDest) {
|
||||
vanillaAoControlBits.clear();
|
||||
final Direction face = quad.lightFace();
|
||||
quad.toVanilla(0, vertexData, 0, false);
|
||||
|
||||
VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, face, vanillaAoData, vanillaAoControlBits);
|
||||
vanillaCalc.fabric_apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, quad.lightFace(), vanillaAoData, vanillaAoControlBits);
|
||||
|
||||
System.arraycopy(vanillaCalc.fabric_colorMultiplier(), 0, aoDest, 0, 4);
|
||||
System.arraycopy(vanillaCalc.fabric_brightness(), 0, lightDest, 0, 4);
|
||||
}
|
||||
|
||||
private void calcFastVanilla(MutableQuadViewImpl quad) {
|
||||
int flags = quad.geometryFlags();
|
||||
|
||||
// force to block face if shape is full cube - matches vanilla logic
|
||||
if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
|
||||
flags |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
|
||||
if ((flags & CUBIC_FLAG) == 0) {
|
||||
vanillaPartialFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
|
||||
} else {
|
||||
vanillaFullFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void calcEnhanced(MutableQuadViewImpl quad) {
|
||||
switch(quad.geometryFlags()) {
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
|
||||
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaPartialFace(quad, true);
|
||||
break;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
|
||||
case AXIS_ALIGNED_FLAG:
|
||||
blendedPartialFace(quad);
|
||||
break;
|
||||
|
||||
default:
|
||||
irregularFace(quad);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
computeFace(lightFace, isOnLightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
|
||||
}
|
||||
|
||||
private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = computeFace(lightFace, isOnLightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
final float[] w = this.w;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
wFunc.apply(quad, i, w);
|
||||
light[i] = faceData.weightedCombinedLight(w);
|
||||
ao[i] = faceData.weigtedAo(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation */
|
||||
AoFaceData tmpFace = new AoFaceData();
|
||||
|
||||
/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face */
|
||||
private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
|
||||
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
|
||||
final float w0 = 1 - w1;
|
||||
return AoFaceData.weightedMean(computeFace(lightFace, true), w0, computeFace(lightFace, false), w1, tmpFace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
|
||||
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
|
||||
*/
|
||||
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
|
||||
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
|
||||
if(MathHelper.approximatelyEquals(w1, 0)) {
|
||||
return computeFace(lightFace, true);
|
||||
} else if(MathHelper.approximatelyEquals(w1, 1)) {
|
||||
return computeFace(lightFace, false);
|
||||
} else {
|
||||
final float w0 = 1 - w1;
|
||||
return AoFaceData.weightedMean(computeFace(lightFace, true), w0, computeFace(lightFace, false), w1, tmpFace);
|
||||
}
|
||||
}
|
||||
|
||||
private void blendedPartialFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
wFunc.apply(quad, i, w);
|
||||
light[i] = faceData.weightedCombinedLight(w);
|
||||
ao[i] = faceData.weigtedAo(w);
|
||||
}
|
||||
}
|
||||
|
||||
/** used exclusively in irregular face to avoid new heap allocations each call. */
|
||||
private final Vector3f vertexNormal = new Vector3f();
|
||||
|
||||
private void irregularFace(MutableQuadViewImpl quad) {
|
||||
final Vector3f faceNorm = quad.faceNormal();
|
||||
Vector3f normal;
|
||||
final float[] w = this.w;
|
||||
final float aoResult[] = this.ao;
|
||||
final int[] lightResult = this.light;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
normal = quad.hasNormal(i) ? quad.copyNormal(i, vertexNormal) : faceNorm;
|
||||
float ao = 0, sky = 0, block = 0, maxAo = 0;
|
||||
int maxSky = 0, maxBlock = 0;
|
||||
|
||||
final float x = normal.getX();
|
||||
if(!MathHelper.approximatelyEquals(0f, x)) {
|
||||
final Direction face = x > 0 ? Direction.EAST : Direction.WEST;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = x * x;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = a;
|
||||
maxSky = s;
|
||||
maxBlock = b;
|
||||
}
|
||||
|
||||
final float y = normal.getY();
|
||||
if(!MathHelper.approximatelyEquals(0f, y)) {
|
||||
final Direction face = y > 0 ? Direction.UP: Direction.DOWN;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = y * y;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
final float z = normal.getZ();
|
||||
if(!MathHelper.approximatelyEquals(0f, z)) {
|
||||
final Direction face = z > 0 ? Direction.SOUTH: Direction.NORTH;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = z * z;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
aoResult[i] = (ao + maxAo) * 0.5f;
|
||||
lightResult[i] = (((int)((sky + maxSky) * 0.5f) & 0xF0) << 16) | ((int)((block + maxBlock) * 0.5f) & 0xF0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes smoothed brightness and Ao shading for four corners of a block face.
|
||||
* Outer block face is what you normally see and what you get get when second
|
||||
* parameter is true. Inner is light *within* the block and usually darker.
|
||||
* It is blended with the outer face for inset surfaces, but is also used directly
|
||||
* in vanilla logic for some blocks that aren't full opaque cubes.
|
||||
* Except for parameterization, the logic itself is practically identical to vanilla.
|
||||
*/
|
||||
private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace) {
|
||||
final int faceDataIndex = isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6;
|
||||
final int mask = 1 << faceDataIndex;
|
||||
final AoFaceData result = faceData[faceDataIndex];
|
||||
if((completionFlags & mask) == 0) {
|
||||
completionFlags |= mask;
|
||||
|
||||
final BlockRenderView world = blockInfo.blockView;
|
||||
final BlockPos pos = blockInfo.blockPos;
|
||||
final BlockPos.Mutable lightPos = this.lightPos;
|
||||
final BlockPos.Mutable searchPos = this.searchPos;
|
||||
|
||||
lightPos.set(isOnBlockFace ? pos.offset(lightFace) : pos);
|
||||
AoFace aoFace = AoFace.get(lightFace);
|
||||
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);
|
||||
final int light0 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao0 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);
|
||||
final int light1 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao1 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);
|
||||
final int light2 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao2 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);
|
||||
final int light3 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao3 = aoFunc.apply(searchPos);
|
||||
|
||||
// vanilla was further offsetting these in the direction of the light face
|
||||
// but it was actually mis-sampling and causing visible artifacts in certain situation
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);//.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear0 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);//.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear1 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);//.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear2 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);//.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear3 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
private void calcEnhanced(MutableQuadViewImpl quad) {
|
||||
switch (quad.geometryFlags()) {
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
|
||||
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaPartialFace(quad, true);
|
||||
break;
|
||||
|
||||
// c = corner - values at corners of face
|
||||
int cLight0, cLight1, cLight2, cLight3;
|
||||
float cAo0, cAo1, cAo2, cAo3;
|
||||
|
||||
// If neighbors on both side of the corner are opaque, then apparently we use the light/shade
|
||||
// from one of the sides adjacent to the corner. If either neighbor is clear (no light subtraction)
|
||||
// then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face)
|
||||
if (!isClear2 && !isClear0) {
|
||||
cAo0 = ao0;
|
||||
cLight0 = light0;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]).setOffset(aoFace.neighbors[2]);
|
||||
cAo0 = aoFunc.apply(searchPos);
|
||||
cLight0 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear3 && !isClear0) {
|
||||
cAo1 = ao0;
|
||||
cLight1 = light0;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]).setOffset(aoFace.neighbors[3]);
|
||||
cAo1 = aoFunc.apply(searchPos);
|
||||
cLight1 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear2 && !isClear1) {
|
||||
cAo2 = ao1;
|
||||
cLight2 = light1;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]).setOffset(aoFace.neighbors[2]);
|
||||
cAo2 = aoFunc.apply(searchPos);
|
||||
cLight2 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear3 && !isClear1) {
|
||||
cAo3 = ao1;
|
||||
cLight3 = light1;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]).setOffset(aoFace.neighbors[3]);
|
||||
cAo3 = aoFunc.apply(searchPos);
|
||||
cLight3 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
// If on block face or neighbor isn't occluding, "center" will be neighbor brightness
|
||||
// Doesn't use light pos because logic not based solely on this block's geometry
|
||||
int lightCenter;
|
||||
searchPos.set((Vec3i)pos).setOffset(lightFace);
|
||||
if (isOnBlockFace || !world.getBlockState(searchPos).isFullOpaque(world, searchPos)) {
|
||||
lightCenter = brightnessFunc.applyAsInt(searchPos);
|
||||
} else {
|
||||
lightCenter = brightnessFunc.applyAsInt(pos);
|
||||
}
|
||||
|
||||
float aoCenter = aoFunc.apply(isOnBlockFace ? lightPos : pos);
|
||||
|
||||
result.a0 = (ao3 + ao0 + cAo1 + aoCenter) * 0.25F;
|
||||
result.a1 = (ao2 + ao0 + cAo0 + aoCenter) * 0.25F;
|
||||
result.a2 = (ao2 + ao1 + cAo2 + aoCenter) * 0.25F;
|
||||
result.a3 = (ao3 + ao1 + cAo3 + aoCenter) * 0.25F;
|
||||
|
||||
result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
|
||||
result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
|
||||
result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
|
||||
result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla code excluded missing light values from mean but was not isotropic.
|
||||
* Still need to substitute or edges are too dark but consistently use the min
|
||||
* value from all four samples.
|
||||
*/
|
||||
private static int meanBrightness(int a, int b, int c, int d) {
|
||||
if(Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
|
||||
return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d);
|
||||
} else {
|
||||
return vanillaMeanBrightness(a, b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
/** vanilla logic - excludes missing light values from mean and has anisotropy defect mentioned above */
|
||||
private static int vanillaMeanBrightness(int a, int b, int c, int d) {
|
||||
if (a == 0)
|
||||
a = d;
|
||||
if (b == 0)
|
||||
b = d;
|
||||
if (c == 0)
|
||||
c = d;
|
||||
// bitwise divide by 4, clamp to expected (positive) range
|
||||
return a + b + c + d >> 2 & 16711935;
|
||||
}
|
||||
|
||||
private static int meanInnerBrightness(int a, int b, int c, int d) {
|
||||
// bitwise divide by 4, clamp to expected (positive) range
|
||||
return a + b + c + d >> 2 & 16711935;
|
||||
}
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
|
||||
case AXIS_ALIGNED_FLAG:
|
||||
blendedPartialFace(quad);
|
||||
break;
|
||||
|
||||
private static int nonZeroMin(int a, int b) {
|
||||
if(a == 0) return b;
|
||||
if(b == 0) return a;
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
private static int meanEdgeBrightness(int a, int b, int c, int d) {
|
||||
final int min = nonZeroMin(nonZeroMin(a, b), nonZeroMin(c, d));
|
||||
return meanInnerBrightness(max(a, min), max(b, min), max(c, min), max(d, min));
|
||||
}
|
||||
default:
|
||||
irregularFace(quad);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
computeFace(lightFace, isOnLightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
|
||||
}
|
||||
|
||||
private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = computeFace(lightFace, isOnLightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
final float[] w = this.w;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
wFunc.apply(quad, i, w);
|
||||
light[i] = faceData.weightedCombinedLight(w);
|
||||
ao[i] = faceData.weigtedAo(w);
|
||||
}
|
||||
}
|
||||
|
||||
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation */
|
||||
AoFaceData tmpFace = new AoFaceData();
|
||||
|
||||
/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face */
|
||||
private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
|
||||
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
|
||||
final float w0 = 1 - w1;
|
||||
return AoFaceData.weightedMean(computeFace(lightFace, true), w0, computeFace(lightFace, false), w1, tmpFace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
|
||||
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
|
||||
*/
|
||||
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
|
||||
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
|
||||
|
||||
if (MathHelper.approximatelyEquals(w1, 0)) {
|
||||
return computeFace(lightFace, true);
|
||||
} else if (MathHelper.approximatelyEquals(w1, 1)) {
|
||||
return computeFace(lightFace, false);
|
||||
} else {
|
||||
final float w0 = 1 - w1;
|
||||
return AoFaceData.weightedMean(computeFace(lightFace, true), w0, computeFace(lightFace, false), w1, tmpFace);
|
||||
}
|
||||
}
|
||||
|
||||
private void blendedPartialFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
wFunc.apply(quad, i, w);
|
||||
light[i] = faceData.weightedCombinedLight(w);
|
||||
ao[i] = faceData.weigtedAo(w);
|
||||
}
|
||||
}
|
||||
|
||||
/** used exclusively in irregular face to avoid new heap allocations each call. */
|
||||
private final Vector3f vertexNormal = new Vector3f();
|
||||
|
||||
private void irregularFace(MutableQuadViewImpl quad) {
|
||||
final Vector3f faceNorm = quad.faceNormal();
|
||||
Vector3f normal;
|
||||
final float[] w = this.w;
|
||||
final float aoResult[] = this.ao;
|
||||
final int[] lightResult = this.light;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
normal = quad.hasNormal(i) ? quad.copyNormal(i, vertexNormal) : faceNorm;
|
||||
float ao = 0, sky = 0, block = 0, maxAo = 0;
|
||||
int maxSky = 0, maxBlock = 0;
|
||||
|
||||
final float x = normal.getX();
|
||||
|
||||
if (!MathHelper.approximatelyEquals(0f, x)) {
|
||||
final Direction face = x > 0 ? Direction.EAST : Direction.WEST;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = x * x;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = a;
|
||||
maxSky = s;
|
||||
maxBlock = b;
|
||||
}
|
||||
|
||||
final float y = normal.getY();
|
||||
|
||||
if (!MathHelper.approximatelyEquals(0f, y)) {
|
||||
final Direction face = y > 0 ? Direction.UP : Direction.DOWN;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = y * y;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
final float z = normal.getZ();
|
||||
|
||||
if (!MathHelper.approximatelyEquals(0f, z)) {
|
||||
final Direction face = z > 0 ? Direction.SOUTH : Direction.NORTH;
|
||||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = z * z;
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
aoResult[i] = (ao + maxAo) * 0.5f;
|
||||
lightResult[i] = (((int) ((sky + maxSky) * 0.5f) & 0xF0) << 16) | ((int) ((block + maxBlock) * 0.5f) & 0xF0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes smoothed brightness and Ao shading for four corners of a block face.
|
||||
* Outer block face is what you normally see and what you get get when second
|
||||
* parameter is true. Inner is light *within* the block and usually darker.
|
||||
* It is blended with the outer face for inset surfaces, but is also used directly
|
||||
* in vanilla logic for some blocks that aren't full opaque cubes.
|
||||
* Except for parameterization, the logic itself is practically identical to vanilla.
|
||||
*/
|
||||
private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace) {
|
||||
final int faceDataIndex = isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6;
|
||||
final int mask = 1 << faceDataIndex;
|
||||
final AoFaceData result = faceData[faceDataIndex];
|
||||
|
||||
if ((completionFlags & mask) == 0) {
|
||||
completionFlags |= mask;
|
||||
|
||||
final BlockRenderView world = blockInfo.blockView;
|
||||
final BlockPos pos = blockInfo.blockPos;
|
||||
final BlockPos.Mutable lightPos = this.lightPos;
|
||||
final BlockPos.Mutable searchPos = this.searchPos;
|
||||
|
||||
lightPos.set(isOnBlockFace ? pos.offset(lightFace) : pos);
|
||||
AoFace aoFace = AoFace.get(lightFace);
|
||||
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);
|
||||
final int light0 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao0 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);
|
||||
final int light1 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao1 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);
|
||||
final int light2 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao2 = aoFunc.apply(searchPos);
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);
|
||||
final int light3 = brightnessFunc.applyAsInt(searchPos);
|
||||
final float ao3 = aoFunc.apply(searchPos);
|
||||
|
||||
// vanilla was further offsetting these in the direction of the light face
|
||||
// but it was actually mis-sampling and causing visible artifacts in certain situation
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);//.setOffset(lightFace);
|
||||
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear0 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);//.setOffset(lightFace);
|
||||
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear1 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);//.setOffset(lightFace);
|
||||
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear2 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);//.setOffset(lightFace);
|
||||
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear3 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
|
||||
|
||||
// c = corner - values at corners of face
|
||||
int cLight0, cLight1, cLight2, cLight3;
|
||||
float cAo0, cAo1, cAo2, cAo3;
|
||||
|
||||
// If neighbors on both side of the corner are opaque, then apparently we use the light/shade
|
||||
// from one of the sides adjacent to the corner. If either neighbor is clear (no light subtraction)
|
||||
// then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face)
|
||||
if (!isClear2 && !isClear0) {
|
||||
cAo0 = ao0;
|
||||
cLight0 = light0;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]).setOffset(aoFace.neighbors[2]);
|
||||
cAo0 = aoFunc.apply(searchPos);
|
||||
cLight0 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear3 && !isClear0) {
|
||||
cAo1 = ao0;
|
||||
cLight1 = light0;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]).setOffset(aoFace.neighbors[3]);
|
||||
cAo1 = aoFunc.apply(searchPos);
|
||||
cLight1 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear2 && !isClear1) {
|
||||
cAo2 = ao1;
|
||||
cLight2 = light1;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]).setOffset(aoFace.neighbors[2]);
|
||||
cAo2 = aoFunc.apply(searchPos);
|
||||
cLight2 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
if (!isClear3 && !isClear1) {
|
||||
cAo3 = ao1;
|
||||
cLight3 = light1;
|
||||
} else {
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]).setOffset(aoFace.neighbors[3]);
|
||||
cAo3 = aoFunc.apply(searchPos);
|
||||
cLight3 = brightnessFunc.applyAsInt(searchPos);
|
||||
}
|
||||
|
||||
// If on block face or neighbor isn't occluding, "center" will be neighbor brightness
|
||||
// Doesn't use light pos because logic not based solely on this block's geometry
|
||||
int lightCenter;
|
||||
searchPos.set((Vec3i) pos).setOffset(lightFace);
|
||||
|
||||
if (isOnBlockFace || !world.getBlockState(searchPos).isFullOpaque(world, searchPos)) {
|
||||
lightCenter = brightnessFunc.applyAsInt(searchPos);
|
||||
} else {
|
||||
lightCenter = brightnessFunc.applyAsInt(pos);
|
||||
}
|
||||
|
||||
float aoCenter = aoFunc.apply(isOnBlockFace ? lightPos : pos);
|
||||
|
||||
result.a0 = (ao3 + ao0 + cAo1 + aoCenter) * 0.25F;
|
||||
result.a1 = (ao2 + ao0 + cAo0 + aoCenter) * 0.25F;
|
||||
result.a2 = (ao2 + ao1 + cAo2 + aoCenter) * 0.25F;
|
||||
result.a3 = (ao3 + ao1 + cAo3 + aoCenter) * 0.25F;
|
||||
|
||||
result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
|
||||
result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
|
||||
result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
|
||||
result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla code excluded missing light values from mean but was not isotropic.
|
||||
* Still need to substitute or edges are too dark but consistently use the min
|
||||
* value from all four samples.
|
||||
*/
|
||||
private static int meanBrightness(int a, int b, int c, int d) {
|
||||
if (Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
|
||||
return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d);
|
||||
} else {
|
||||
return vanillaMeanBrightness(a, b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
/** vanilla logic - excludes missing light values from mean and has anisotropy defect mentioned above */
|
||||
private static int vanillaMeanBrightness(int a, int b, int c, int d) {
|
||||
if (a == 0) a = d;
|
||||
if (b == 0) b = d;
|
||||
if (c == 0) c = d;
|
||||
// bitwise divide by 4, clamp to expected (positive) range
|
||||
return a + b + c + d >> 2 & 16711935;
|
||||
}
|
||||
|
||||
private static int meanInnerBrightness(int a, int b, int c, int d) {
|
||||
// bitwise divide by 4, clamp to expected (positive) range
|
||||
return a + b + c + d >> 2 & 16711935;
|
||||
}
|
||||
|
||||
private static int nonZeroMin(int a, int b) {
|
||||
if (a == 0) return b;
|
||||
if (b == 0) return a;
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
private static int meanEdgeBrightness(int a, int b, int c, int d) {
|
||||
final int min = nonZeroMin(nonZeroMin(a, b), nonZeroMin(c, d));
|
||||
return meanInnerBrightness(max(a, min), max(b, min), max(c, min), max(d, min));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,42 +21,42 @@ package net.fabricmc.indigo.renderer.aocalc;
|
|||
* This determine the appearance of smooth lighting.
|
||||
*/
|
||||
public enum AoConfig {
|
||||
/**
|
||||
* Quads will be lit with a slightly modified copy of the vanilla ambient
|
||||
* occlusion calculator. Quads with triangles, non-square or slopes will
|
||||
* not look good in this model. This model also requires a fixed vertex
|
||||
* winding order for all quads.
|
||||
*/
|
||||
VANILLA,
|
||||
|
||||
/**
|
||||
* Quads are lit with enhanced lighting logic. Enhanced lighting will be
|
||||
* similar to vanilla lighting for face-aligned quads, and will be different
|
||||
* (generally better) for triangles, non-square and sloped quads. Axis-
|
||||
* aligned quads not on the block face will have interpolated brightness based
|
||||
* on depth instead of the all-or-nothing brightness of vanilla.<p>
|
||||
*
|
||||
* Non-vanilla quads can have vertices in any (counter-clockwise) order.<p>
|
||||
*/
|
||||
ENHANCED,
|
||||
|
||||
/**
|
||||
* Enhanced lighting is configured to mimic vanilla lighting. Results will be
|
||||
* identical to vanilla except that non-square quads, triangles, etc. will
|
||||
* not be sensitive to vertex order. However shading will not be interpolated
|
||||
* as it is with enhanced. These quads do not occur in vanilla models.
|
||||
* Not recommended for models with complex geometry, but may be faster than
|
||||
* the vanilla calculator when vanilla lighting is desired.
|
||||
*/
|
||||
EMULATE,
|
||||
|
||||
/**
|
||||
* Quads from vanilla models are lit using {@link #EMULATE} mode and all
|
||||
* other quads are lit using {@link #ENHANCED} mode. This mode ensures
|
||||
* all vanilla models retain their normal appearance while providing
|
||||
* better lighting for models with more complex geometry. However,
|
||||
* inconsistencies may be visible when vanilla and non-vanilla models are
|
||||
* near each other.
|
||||
*/
|
||||
HYBRID;
|
||||
/**
|
||||
* Quads will be lit with a slightly modified copy of the vanilla ambient
|
||||
* occlusion calculator. Quads with triangles, non-square or slopes will
|
||||
* not look good in this model. This model also requires a fixed vertex
|
||||
* winding order for all quads.
|
||||
*/
|
||||
VANILLA,
|
||||
|
||||
/**
|
||||
* Quads are lit with enhanced lighting logic. Enhanced lighting will be
|
||||
* similar to vanilla lighting for face-aligned quads, and will be different
|
||||
* (generally better) for triangles, non-square and sloped quads. Axis-
|
||||
* aligned quads not on the block face will have interpolated brightness based
|
||||
* on depth instead of the all-or-nothing brightness of vanilla.<p>
|
||||
*
|
||||
* Non-vanilla quads can have vertices in any (counter-clockwise) order.<p>
|
||||
*/
|
||||
ENHANCED,
|
||||
|
||||
/**
|
||||
* Enhanced lighting is configured to mimic vanilla lighting. Results will be
|
||||
* identical to vanilla except that non-square quads, triangles, etc. will
|
||||
* not be sensitive to vertex order. However shading will not be interpolated
|
||||
* as it is with enhanced. These quads do not occur in vanilla models.
|
||||
* Not recommended for models with complex geometry, but may be faster than
|
||||
* the vanilla calculator when vanilla lighting is desired.
|
||||
*/
|
||||
EMULATE,
|
||||
|
||||
/**
|
||||
* Quads from vanilla models are lit using {@link #EMULATE} mode and all
|
||||
* other quads are lit using {@link #ENHANCED} mode. This mode ensures
|
||||
* all vanilla models retain their normal appearance while providing
|
||||
* better lighting for models with more complex geometry. However,
|
||||
* inconsistencies may be visible when vanilla and non-vanilla models are
|
||||
* near each other.
|
||||
*/
|
||||
HYBRID;
|
||||
}
|
||||
|
|
|
@ -28,101 +28,90 @@ import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
|||
/**
|
||||
* Adapted from vanilla BlockModelRenderer.AoCalculator.
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Environment(EnvType.CLIENT)
|
||||
enum AoFace {
|
||||
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.y(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1-u) * v;
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = u * (1-v);
|
||||
w[3] = u * v;
|
||||
}),
|
||||
AOF_UP(new Direction[]{EAST, WEST, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.y(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1-v);
|
||||
w[2] = (1-u) * (1-v);
|
||||
w[3] = (1-u) * v;
|
||||
}),
|
||||
AOF_NORTH(new Direction[]{UP, DOWN, EAST, WEST}, (q, i) -> CLAMP_FUNC.clamp(q.z(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1-v);
|
||||
w[1] = u * v;
|
||||
w[2] = (1-u) * v;
|
||||
w[3] = (1-u) * (1-v);
|
||||
}),
|
||||
AOF_SOUTH(new Direction[]{WEST, EAST, DOWN, UP}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.z(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1-v);
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = (1-u) * v;
|
||||
w[3] = u * v;
|
||||
}),
|
||||
AOF_WEST(new Direction[]{UP, DOWN, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.x(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1-v);
|
||||
w[2] = (1-u) * (1-v);
|
||||
w[3] = (1-u) * v;
|
||||
}),
|
||||
AOF_EAST(new Direction[]{DOWN, UP, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.x(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1-u) * v;
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = u * (1-v);
|
||||
w[3] = u * v;
|
||||
});
|
||||
AOF_DOWN(new Direction[] { WEST, EAST, NORTH, SOUTH }, (q, i) -> CLAMP_FUNC.clamp(q.y(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1 - u) * v;
|
||||
w[1] = (1 - u) * (1 - v);
|
||||
w[2] = u * (1 - v);
|
||||
w[3] = u * v;}),
|
||||
AOF_UP(new Direction[] { EAST, WEST, NORTH, SOUTH }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.y(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1 - v);
|
||||
w[2] = (1 - u) * (1 - v);
|
||||
w[3] = (1 - u) * v;}),
|
||||
AOF_NORTH(new Direction[] { UP, DOWN, EAST, WEST }, (q, i) -> CLAMP_FUNC.clamp(q.z(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1 - v);
|
||||
w[1] = u * v;
|
||||
w[2] = (1 - u) * v;
|
||||
w[3] = (1 - u) * (1 - v);}),
|
||||
AOF_SOUTH(new Direction[] { WEST, EAST, DOWN, UP }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.z(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1 - v);
|
||||
w[1] = (1 - u) * (1 - v);
|
||||
w[2] = (1 - u) * v;
|
||||
w[3] = u * v;}),
|
||||
AOF_WEST(new Direction[] { UP, DOWN, NORTH, SOUTH }, (q, i) -> CLAMP_FUNC.clamp(q.x(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1 - v);
|
||||
w[2] = (1 - u) * (1 - v);
|
||||
w[3] = (1 - u) * v;}),
|
||||
AOF_EAST(new Direction[] { DOWN, UP, NORTH, SOUTH }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.x(i)), (q, i, w) -> {
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1 - u) * v;
|
||||
w[1] = (1 - u) * (1 - v);
|
||||
w[2] = u * (1 - v);
|
||||
w[3] = u * v;
|
||||
});
|
||||
|
||||
final Direction[] neighbors;
|
||||
final WeightFunction weightFunc;
|
||||
final Vertex2Float depthFunc;
|
||||
|
||||
private AoFace(Direction[] faces, Vertex2Float depthFunc, WeightFunction weightFunc) {
|
||||
this.neighbors = faces;
|
||||
this.depthFunc = depthFunc;
|
||||
this.weightFunc = weightFunc;
|
||||
}
|
||||
|
||||
private static final AoFace[] values = (AoFace[])SystemUtil.consume(new AoFace[6], (neighborData) -> {
|
||||
neighborData[DOWN.getId()] = AOF_DOWN;
|
||||
neighborData[UP.getId()] = AOF_UP;
|
||||
neighborData[NORTH.getId()] = AOF_NORTH;
|
||||
neighborData[SOUTH.getId()] = AOF_SOUTH;
|
||||
neighborData[WEST.getId()] = AOF_WEST;
|
||||
neighborData[EAST.getId()] = AOF_EAST;
|
||||
});
|
||||
final Direction[] neighbors;
|
||||
final WeightFunction weightFunc;
|
||||
final Vertex2Float depthFunc;
|
||||
|
||||
public static AoFace get(Direction direction){
|
||||
return values[direction.getId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations handle bilinear interpolation of a point on a light face
|
||||
* by computing weights for each corner of the light face. Relies on the fact
|
||||
* that each face is a unit cube. Uses coordinates from axes orthogonal to face
|
||||
* as distance from the edge of the cube, flipping as needed. Multiplying distance
|
||||
* coordinate pairs together gives sub-area that are the corner weights.
|
||||
* Weights sum to 1 because it is a unit cube. Values are stored in the provided array.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
static interface WeightFunction {
|
||||
void apply(QuadViewImpl q, int vertexIndex, float[] out);
|
||||
}
|
||||
private AoFace(Direction[] faces, Vertex2Float depthFunc, WeightFunction weightFunc) {
|
||||
this.neighbors = faces;
|
||||
this.depthFunc = depthFunc;
|
||||
this.weightFunc = weightFunc;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
static interface Vertex2Float {
|
||||
float apply(QuadViewImpl q, int vertexIndex);
|
||||
}
|
||||
private static final AoFace[] values = (AoFace[]) SystemUtil.consume(new AoFace[6], (neighborData) -> {
|
||||
neighborData[DOWN.getId()] = AOF_DOWN;
|
||||
neighborData[UP.getId()] = AOF_UP;
|
||||
neighborData[NORTH.getId()] = AOF_NORTH;
|
||||
neighborData[SOUTH.getId()] = AOF_SOUTH;
|
||||
neighborData[WEST.getId()] = AOF_WEST;
|
||||
neighborData[EAST.getId()] = AOF_EAST;
|
||||
});
|
||||
|
||||
public static AoFace get(Direction direction) {
|
||||
return values[direction.getId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations handle bilinear interpolation of a point on a light face
|
||||
* by computing weights for each corner of the light face. Relies on the fact
|
||||
* that each face is a unit cube. Uses coordinates from axes orthogonal to face
|
||||
* as distance from the edge of the cube, flipping as needed. Multiplying distance
|
||||
* coordinate pairs together gives sub-area that are the corner weights.
|
||||
* Weights sum to 1 because it is a unit cube. Values are stored in the provided array.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
static interface WeightFunction {
|
||||
void apply(QuadViewImpl q, int vertexIndex, float[] out);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
static interface Vertex2Float {
|
||||
float apply(QuadViewImpl q, int vertexIndex);
|
||||
}
|
||||
}
|
|
@ -21,82 +21,82 @@ package net.fabricmc.indigo.renderer.aocalc;
|
|||
* Handles caching and provides various utility methods to simplify code elsewhere.
|
||||
*/
|
||||
class AoFaceData {
|
||||
float a0;
|
||||
float a1;
|
||||
float a2;
|
||||
float a3;
|
||||
int b0;
|
||||
int b1;
|
||||
int b2;
|
||||
int b3;
|
||||
int s0;
|
||||
int s1;
|
||||
int s2;
|
||||
int s3;
|
||||
|
||||
void l0(int l0) {
|
||||
this.b0 = l0 & 0xFFFF;
|
||||
this.s0 = (l0 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l1(int l1) {
|
||||
this.b1 = l1 & 0xFFFF;
|
||||
this.s1 = (l1 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l2(int l2) {
|
||||
this.b2 = l2 & 0xFFFF;
|
||||
this.s2 = (l2 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l3(int l3) {
|
||||
this.b3 = l3 & 0xFFFF;
|
||||
this.s3 = (l3 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
int weigtedBlockLight(float[] w) {
|
||||
return (int) (b0 * w[0] + b1 * w[1] + b2 * w[2] + b3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int weigtedSkyLight(float[] w) {
|
||||
return (int) (s0 * w[0] + s1 * w[1] + s2 * w[2] + s3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int weightedCombinedLight(float[] w) {
|
||||
return weigtedSkyLight(w) << 16 | weigtedBlockLight(w);
|
||||
}
|
||||
|
||||
float weigtedAo(float[] w) {
|
||||
return a0 * w[0] + a1 * w[1] + a2 * w[2] + a3 * w[3];
|
||||
}
|
||||
|
||||
void toArray(float[] aOut, int[] bOut, int[] vertexMap) {
|
||||
aOut[vertexMap[0]] = a0;
|
||||
aOut[vertexMap[1]] = a1;
|
||||
aOut[vertexMap[2]] = a2;
|
||||
aOut[vertexMap[3]] = a3;
|
||||
bOut[vertexMap[0]] = s0 << 16 | b0;
|
||||
bOut[vertexMap[1]] = s1 << 16 | b1;
|
||||
bOut[vertexMap[2]] = s2 << 16 | b2;
|
||||
bOut[vertexMap[3]] = s3 << 16 | b3;
|
||||
}
|
||||
|
||||
static AoFaceData weightedMean(AoFaceData in0, float w0, AoFaceData in1, float w1, AoFaceData out) {
|
||||
out.a0 = in0.a0 * w0 + in1.a0 * w1;
|
||||
out.a1 = in0.a1 * w0 + in1.a1 * w1;
|
||||
out.a2 = in0.a2 * w0 + in1.a2 * w1;
|
||||
out.a3 = in0.a3 * w0 + in1.a3 * w1;
|
||||
|
||||
out.b0 = (int) (in0.b0 * w0 + in1.b0 * w1);
|
||||
out.b1 = (int) (in0.b1 * w0 + in1.b1 * w1);
|
||||
out.b2 = (int) (in0.b2 * w0 + in1.b2 * w1);
|
||||
out.b3 = (int) (in0.b3 * w0 + in1.b3 * w1);
|
||||
|
||||
out.s0 = (int) (in0.s0 * w0 + in1.s0 * w1);
|
||||
out.s1 = (int) (in0.s1 * w0 + in1.s1 * w1);
|
||||
out.s2 = (int) (in0.s2 * w0 + in1.s2 * w1);
|
||||
out.s3 = (int) (in0.s3 * w0 + in1.s3 * w1);
|
||||
|
||||
return out;
|
||||
}
|
||||
float a0;
|
||||
float a1;
|
||||
float a2;
|
||||
float a3;
|
||||
int b0;
|
||||
int b1;
|
||||
int b2;
|
||||
int b3;
|
||||
int s0;
|
||||
int s1;
|
||||
int s2;
|
||||
int s3;
|
||||
|
||||
void l0(int l0) {
|
||||
this.b0 = l0 & 0xFFFF;
|
||||
this.s0 = (l0 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l1(int l1) {
|
||||
this.b1 = l1 & 0xFFFF;
|
||||
this.s1 = (l1 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l2(int l2) {
|
||||
this.b2 = l2 & 0xFFFF;
|
||||
this.s2 = (l2 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
void l3(int l3) {
|
||||
this.b3 = l3 & 0xFFFF;
|
||||
this.s3 = (l3 >>> 16) & 0xFFFF;
|
||||
}
|
||||
|
||||
int weigtedBlockLight(float[] w) {
|
||||
return (int) (b0 * w[0] + b1 * w[1] + b2 * w[2] + b3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int weigtedSkyLight(float[] w) {
|
||||
return (int) (s0 * w[0] + s1 * w[1] + s2 * w[2] + s3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int weightedCombinedLight(float[] w) {
|
||||
return weigtedSkyLight(w) << 16 | weigtedBlockLight(w);
|
||||
}
|
||||
|
||||
float weigtedAo(float[] w) {
|
||||
return a0 * w[0] + a1 * w[1] + a2 * w[2] + a3 * w[3];
|
||||
}
|
||||
|
||||
void toArray(float[] aOut, int[] bOut, int[] vertexMap) {
|
||||
aOut[vertexMap[0]] = a0;
|
||||
aOut[vertexMap[1]] = a1;
|
||||
aOut[vertexMap[2]] = a2;
|
||||
aOut[vertexMap[3]] = a3;
|
||||
bOut[vertexMap[0]] = s0 << 16 | b0;
|
||||
bOut[vertexMap[1]] = s1 << 16 | b1;
|
||||
bOut[vertexMap[2]] = s2 << 16 | b2;
|
||||
bOut[vertexMap[3]] = s3 << 16 | b3;
|
||||
}
|
||||
|
||||
static AoFaceData weightedMean(AoFaceData in0, float w0, AoFaceData in1, float w1, AoFaceData out) {
|
||||
out.a0 = in0.a0 * w0 + in1.a0 * w1;
|
||||
out.a1 = in0.a1 * w0 + in1.a1 * w1;
|
||||
out.a2 = in0.a2 * w0 + in1.a2 * w1;
|
||||
out.a3 = in0.a3 * w0 + in1.a3 * w1;
|
||||
|
||||
out.b0 = (int) (in0.b0 * w0 + in1.b0 * w1);
|
||||
out.b1 = (int) (in0.b1 * w0 + in1.b1 * w1);
|
||||
out.b2 = (int) (in0.b2 * w0 + in1.b2 * w1);
|
||||
out.b3 = (int) (in0.b3 * w0 + in1.b3 * w1);
|
||||
|
||||
out.s0 = (int) (in0.s0 * w0 + in1.s0 * w1);
|
||||
out.s1 = (int) (in0.s1 * w0 + in1.s1 * w1);
|
||||
out.s2 = (int) (in0.s2 * w0 + in1.s2 * w1);
|
||||
out.s3 = (int) (in0.s3 * w0 + in1.s3 * w1);
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -27,16 +27,16 @@ import net.minecraft.world.BlockView;
|
|||
*/
|
||||
@FunctionalInterface
|
||||
public interface AoLuminanceFix {
|
||||
float apply(BlockView view, BlockPos pos);
|
||||
|
||||
AoLuminanceFix INSTANCE = Indigo.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla;
|
||||
|
||||
static float vanilla(BlockView view, BlockPos pos) {
|
||||
return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos);
|
||||
}
|
||||
|
||||
static float fixed(BlockView view, BlockPos pos) {
|
||||
final BlockState state = view.getBlockState(pos);
|
||||
return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f;
|
||||
}
|
||||
float apply(BlockView view, BlockPos pos);
|
||||
|
||||
AoLuminanceFix INSTANCE = Indigo.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla;
|
||||
|
||||
static float vanilla(BlockView view, BlockPos pos) {
|
||||
return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos);
|
||||
}
|
||||
|
||||
static float fixed(BlockView view, BlockPos pos) {
|
||||
final BlockState state = view.getBlockState(pos);
|
||||
return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import net.fabricmc.indigo.Indigo;
|
|||
@Environment(EnvType.CLIENT)
|
||||
@FunctionalInterface
|
||||
interface AoVertexClampFunction {
|
||||
float clamp(float x);
|
||||
float clamp(float x);
|
||||
|
||||
AoVertexClampFunction CLAMP_FUNC = Indigo.FIX_EXTERIOR_VERTEX_LIGHTING ? x -> x < 0f ? 0f : (x > 1f ? 1f : x) : x -> x;
|
||||
AoVertexClampFunction CLAMP_FUNC = Indigo.FIX_EXTERIOR_VERTEX_LIGHTING ? x -> x < 0f ? 0f : (x > 1f ? 1f : x) : x -> x;
|
||||
}
|
|
@ -1,371 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.aocalc;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator.AoFunc;
|
||||
import net.fabricmc.indigo.renderer.render.BlockRenderInfo;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.SystemUtil;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Vec3i;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
/**
|
||||
* Copy of vanilla AoCalculator modified to output to use parameterized
|
||||
* outputs and brightness function.
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class VanillaAoCalc {
|
||||
private int[] vertexData = new int[28];
|
||||
private float[] aoBounds = new float[12];
|
||||
private final ToIntFunction<BlockPos> brightnessFunc;
|
||||
private final AoFunc aoFunc;
|
||||
|
||||
public VanillaAoCalc(ToIntFunction<BlockPos> brightnessFunc, AoFunc aoFunc) {
|
||||
this.brightnessFunc = brightnessFunc;
|
||||
this.aoFunc = aoFunc;
|
||||
}
|
||||
|
||||
public void compute(BlockRenderInfo blockInfo, QuadView quad, float ao[], int[] brightness) {
|
||||
BitSet bits = new BitSet(3);
|
||||
quad.toVanilla(0, vertexData, 0, false);
|
||||
updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, quad.lightFace(), aoBounds, bits);
|
||||
apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, quad.lightFace(), aoBounds, bits, ao, brightness);
|
||||
}
|
||||
|
||||
private void apply(BlockRenderView blockView, BlockState blockState, BlockPos blockPos, Direction side,
|
||||
float[] aoBounds, BitSet bits, float[] ao, int brightness[]) {
|
||||
BlockPos lightPos = bits.get(0) ? blockPos.offset(side) : blockPos;
|
||||
NeighborData neighborData = NeighborData.getData(side);
|
||||
BlockPos.Mutable mpos = new BlockPos.Mutable();
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[0]);
|
||||
int int_1 = brightnessFunc.applyAsInt(mpos);
|
||||
float float_1 = aoFunc.apply(mpos);
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]);
|
||||
int int_2 = brightnessFunc.applyAsInt(mpos);
|
||||
float float_2 = aoFunc.apply(mpos);
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[2]);
|
||||
int int_3 = brightnessFunc.applyAsInt(mpos);
|
||||
float float_3 = aoFunc.apply(mpos);
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[3]);
|
||||
int int_4 = brightnessFunc.applyAsInt(mpos);
|
||||
float float_4 = aoFunc.apply(mpos);
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[0]).setOffset(side);
|
||||
boolean boolean_1 = blockView.getBlockState(mpos).getOpacity(blockView, mpos) == 0;
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]).setOffset(side);
|
||||
boolean boolean_2 = blockView.getBlockState(mpos).getOpacity(blockView, mpos) == 0;
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[2]).setOffset(side);
|
||||
boolean boolean_3 = blockView.getBlockState(mpos).getOpacity(blockView, mpos) == 0;
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[3]).setOffset(side);
|
||||
boolean boolean_4 = blockView.getBlockState(mpos).getOpacity(blockView, mpos) == 0;
|
||||
float float_6;
|
||||
int int_6;
|
||||
if (!boolean_3 && !boolean_1) {
|
||||
float_6 = float_1;
|
||||
int_6 = int_1;
|
||||
} else {
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[0]).setOffset(neighborData.faces[2]);
|
||||
float_6 = aoFunc.apply(mpos);
|
||||
int_6 = brightnessFunc.applyAsInt(mpos);
|
||||
}
|
||||
|
||||
float float_8;
|
||||
int int_8;
|
||||
if (!boolean_4 && !boolean_1) {
|
||||
float_8 = float_1;
|
||||
int_8 = int_1;
|
||||
} else {
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[0]).setOffset(neighborData.faces[3]);
|
||||
float_8 = aoFunc.apply(mpos);
|
||||
int_8 = brightnessFunc.applyAsInt(mpos);
|
||||
}
|
||||
|
||||
float float_10;
|
||||
int int_10;
|
||||
if (!boolean_3 && !boolean_2) {
|
||||
float_10 = float_2;
|
||||
int_10 = int_2;
|
||||
} else {
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]).setOffset(neighborData.faces[2]);
|
||||
float_10 = aoFunc.apply(mpos);
|
||||
int_10 = brightnessFunc.applyAsInt(mpos);
|
||||
}
|
||||
|
||||
float float_12;
|
||||
int int_12;
|
||||
if (!boolean_4 && !boolean_2) {
|
||||
float_12 = float_2;
|
||||
int_12 = int_2;
|
||||
} else {
|
||||
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]).setOffset(neighborData.faces[3]);
|
||||
float_12 = aoFunc.apply(mpos);
|
||||
int_12 = brightnessFunc.applyAsInt(mpos);
|
||||
}
|
||||
|
||||
int int_13 = brightnessFunc.applyAsInt(blockPos);
|
||||
mpos.set((Vec3i)blockPos).setOffset(side);
|
||||
if (bits.get(0) || !blockView.getBlockState(mpos).isFullOpaque(blockView, mpos)) {
|
||||
int_13 = brightnessFunc.applyAsInt(mpos);
|
||||
}
|
||||
|
||||
float float_13 = bits.get(0) ? blockView.getBlockState(lightPos).getAmbientOcclusionLightLevel(blockView, lightPos) : blockView.getBlockState(blockPos).getAmbientOcclusionLightLevel(blockView, blockPos);
|
||||
Translation blockModelRenderer$Translation_1 = Translation.getTranslations(side);
|
||||
float float_14;
|
||||
float float_15;
|
||||
float float_16;
|
||||
float float_17;
|
||||
if (bits.get(1) && neighborData.nonCubicWeight) {
|
||||
float_14 = (float_4 + float_1 + float_8 + float_13) * 0.25F;
|
||||
float_15 = (float_3 + float_1 + float_6 + float_13) * 0.25F;
|
||||
float_16 = (float_3 + float_2 + float_10 + float_13) * 0.25F;
|
||||
float_17 = (float_4 + float_2 + float_12 + float_13) * 0.25F;
|
||||
float float_22 = aoBounds[neighborData.field_4192[0].shape] * aoBounds[neighborData.field_4192[1].shape];
|
||||
float float_23 = aoBounds[neighborData.field_4192[2].shape] * aoBounds[neighborData.field_4192[3].shape];
|
||||
float float_24 = aoBounds[neighborData.field_4192[4].shape] * aoBounds[neighborData.field_4192[5].shape];
|
||||
float float_25 = aoBounds[neighborData.field_4192[6].shape] * aoBounds[neighborData.field_4192[7].shape];
|
||||
float float_26 = aoBounds[neighborData.field_4185[0].shape] * aoBounds[neighborData.field_4185[1].shape];
|
||||
float float_27 = aoBounds[neighborData.field_4185[2].shape] * aoBounds[neighborData.field_4185[3].shape];
|
||||
float float_28 = aoBounds[neighborData.field_4185[4].shape] * aoBounds[neighborData.field_4185[5].shape];
|
||||
float float_29 = aoBounds[neighborData.field_4185[6].shape] * aoBounds[neighborData.field_4185[7].shape];
|
||||
float float_30 = aoBounds[neighborData.field_4180[0].shape] * aoBounds[neighborData.field_4180[1].shape];
|
||||
float float_31 = aoBounds[neighborData.field_4180[2].shape] * aoBounds[neighborData.field_4180[3].shape];
|
||||
float float_32 = aoBounds[neighborData.field_4180[4].shape] * aoBounds[neighborData.field_4180[5].shape];
|
||||
float float_33 = aoBounds[neighborData.field_4180[6].shape] * aoBounds[neighborData.field_4180[7].shape];
|
||||
float float_34 = aoBounds[neighborData.field_4188[0].shape] * aoBounds[neighborData.field_4188[1].shape];
|
||||
float float_35 = aoBounds[neighborData.field_4188[2].shape] * aoBounds[neighborData.field_4188[3].shape];
|
||||
float float_36 = aoBounds[neighborData.field_4188[4].shape] * aoBounds[neighborData.field_4188[5].shape];
|
||||
float float_37 = aoBounds[neighborData.field_4188[6].shape] * aoBounds[neighborData.field_4188[7].shape];
|
||||
|
||||
ao[blockModelRenderer$Translation_1.firstCorner] = float_14 * float_22 + float_15 * float_23 + float_16 * float_24 + float_17 * float_25;
|
||||
ao[blockModelRenderer$Translation_1.secondCorner] = float_14 * float_26 + float_15 * float_27 + float_16 * float_28 + float_17 * float_29;
|
||||
ao[blockModelRenderer$Translation_1.thirdCorner] = float_14 * float_30 + float_15 * float_31 + float_16 * float_32 + float_17 * float_33;
|
||||
ao[blockModelRenderer$Translation_1.fourthCorner] = float_14 * float_34 + float_15 * float_35 + float_16 * float_36 + float_17 * float_37;
|
||||
int int_14 = this.getAmbientOcclusionBrightness(int_4, int_1, int_8, int_13);
|
||||
int int_15 = this.getAmbientOcclusionBrightness(int_3, int_1, int_6, int_13);
|
||||
int int_16 = this.getAmbientOcclusionBrightness(int_3, int_2, int_10, int_13);
|
||||
int int_17 = this.getAmbientOcclusionBrightness(int_4, int_2, int_12, int_13);
|
||||
brightness[blockModelRenderer$Translation_1.firstCorner] = this.getBrightness(int_14, int_15, int_16, int_17, float_22, float_23, float_24, float_25);
|
||||
brightness[blockModelRenderer$Translation_1.secondCorner] = this.getBrightness(int_14, int_15, int_16, int_17, float_26, float_27, float_28, float_29);
|
||||
brightness[blockModelRenderer$Translation_1.thirdCorner] = this.getBrightness(int_14, int_15, int_16, int_17, float_30, float_31, float_32, float_33);
|
||||
brightness[blockModelRenderer$Translation_1.fourthCorner] = this.getBrightness(int_14, int_15, int_16, int_17, float_34, float_35, float_36, float_37);
|
||||
} else {
|
||||
float_14 = (float_4 + float_1 + float_8 + float_13) * 0.25F;
|
||||
float_15 = (float_3 + float_1 + float_6 + float_13) * 0.25F;
|
||||
float_16 = (float_3 + float_2 + float_10 + float_13) * 0.25F;
|
||||
float_17 = (float_4 + float_2 + float_12 + float_13) * 0.25F;
|
||||
brightness[blockModelRenderer$Translation_1.firstCorner] = this.getAmbientOcclusionBrightness(int_4, int_1, int_8, int_13);
|
||||
brightness[blockModelRenderer$Translation_1.secondCorner] = this.getAmbientOcclusionBrightness(int_3, int_1, int_6, int_13);
|
||||
brightness[blockModelRenderer$Translation_1.thirdCorner] = this.getAmbientOcclusionBrightness(int_3, int_2, int_10, int_13);
|
||||
brightness[blockModelRenderer$Translation_1.fourthCorner] = this.getAmbientOcclusionBrightness(int_4, int_2, int_12, int_13);
|
||||
|
||||
ao[blockModelRenderer$Translation_1.firstCorner] = float_14;
|
||||
ao[blockModelRenderer$Translation_1.secondCorner] = float_15;
|
||||
ao[blockModelRenderer$Translation_1.thirdCorner] = float_16;
|
||||
ao[blockModelRenderer$Translation_1.fourthCorner] = float_17;
|
||||
}
|
||||
}
|
||||
|
||||
private int getAmbientOcclusionBrightness(int int_1, int int_2, int int_3, int int_4) {
|
||||
if (int_1 == 0) {
|
||||
int_1 = int_4;
|
||||
}
|
||||
|
||||
if (int_2 == 0) {
|
||||
int_2 = int_4;
|
||||
}
|
||||
|
||||
if (int_3 == 0) {
|
||||
int_3 = int_4;
|
||||
}
|
||||
|
||||
return int_1 + int_2 + int_3 + int_4 >> 2 & 16711935;
|
||||
}
|
||||
|
||||
private int getBrightness(int int_1, int int_2, int int_3, int int_4, float float_1, float float_2, float float_3, float float_4) {
|
||||
int int_5 = (int)((float)(int_1 >> 16 & 255) * float_1 + (float)(int_2 >> 16 & 255) * float_2 + (float)(int_3 >> 16 & 255) * float_3 + (float)(int_4 >> 16 & 255) * float_4) & 255;
|
||||
int int_6 = (int)((float)(int_1 & 255) * float_1 + (float)(int_2 & 255) * float_2 + (float)(int_3 & 255) * float_3 + (float)(int_4 & 255) * float_4) & 255;
|
||||
return int_5 << 16 | int_6;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
static enum Translation {
|
||||
DOWN(0, 1, 2, 3),
|
||||
UP(2, 3, 0, 1),
|
||||
NORTH(3, 0, 1, 2),
|
||||
SOUTH(0, 1, 2, 3),
|
||||
WEST(3, 0, 1, 2),
|
||||
EAST(1, 2, 3, 0);
|
||||
|
||||
private final int firstCorner;
|
||||
private final int secondCorner;
|
||||
private final int thirdCorner;
|
||||
private final int fourthCorner;
|
||||
private static final Translation[] VALUES = (Translation[])SystemUtil.consume(new Translation[6], (blockModelRenderer$Translations_1) -> {
|
||||
blockModelRenderer$Translations_1[Direction.DOWN.getId()] = DOWN;
|
||||
blockModelRenderer$Translations_1[Direction.UP.getId()] = UP;
|
||||
blockModelRenderer$Translations_1[Direction.NORTH.getId()] = NORTH;
|
||||
blockModelRenderer$Translations_1[Direction.SOUTH.getId()] = SOUTH;
|
||||
blockModelRenderer$Translations_1[Direction.WEST.getId()] = WEST;
|
||||
blockModelRenderer$Translations_1[Direction.EAST.getId()] = EAST;
|
||||
});
|
||||
|
||||
private Translation(int int_1, int int_2, int int_3, int int_4) {
|
||||
this.firstCorner = int_1;
|
||||
this.secondCorner = int_2;
|
||||
this.thirdCorner = int_3;
|
||||
this.fourthCorner = int_4;
|
||||
}
|
||||
|
||||
public static Translation getTranslations(Direction direction_1) {
|
||||
return VALUES[direction_1.getId()];
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static enum NeighborData {
|
||||
DOWN(new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH}, 0.5F, true, new NeighborOrientation[]{NeighborOrientation.FLIP_WEST, NeighborOrientation.SOUTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.WEST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.WEST, NeighborOrientation.SOUTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_WEST, NeighborOrientation.NORTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.WEST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.WEST, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_EAST, NeighborOrientation.NORTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.EAST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.EAST, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_EAST, NeighborOrientation.SOUTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.EAST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.EAST, NeighborOrientation.SOUTH}),
|
||||
UP(new Direction[]{Direction.EAST, Direction.WEST, Direction.NORTH, Direction.SOUTH}, 1.0F, true, new NeighborOrientation[]{NeighborOrientation.EAST, NeighborOrientation.SOUTH, NeighborOrientation.EAST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.SOUTH}, new NeighborOrientation[]{NeighborOrientation.EAST, NeighborOrientation.NORTH, NeighborOrientation.EAST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_EAST, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.WEST, NeighborOrientation.NORTH, NeighborOrientation.WEST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.WEST, NeighborOrientation.SOUTH, NeighborOrientation.WEST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_WEST, NeighborOrientation.SOUTH}),
|
||||
NORTH(new Direction[]{Direction.UP, Direction.DOWN, Direction.EAST, Direction.WEST}, 0.8F, true, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.FLIP_WEST, NeighborOrientation.UP, NeighborOrientation.WEST, NeighborOrientation.FLIP_UP, NeighborOrientation.WEST, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_WEST}, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.FLIP_EAST, NeighborOrientation.UP, NeighborOrientation.EAST, NeighborOrientation.FLIP_UP, NeighborOrientation.EAST, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_EAST}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.FLIP_EAST, NeighborOrientation.DOWN, NeighborOrientation.EAST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.EAST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_EAST}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.FLIP_WEST, NeighborOrientation.DOWN, NeighborOrientation.WEST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.WEST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_WEST}),
|
||||
SOUTH(new Direction[]{Direction.WEST, Direction.EAST, Direction.DOWN, Direction.UP}, 0.8F, true, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_UP, NeighborOrientation.WEST, NeighborOrientation.UP, NeighborOrientation.WEST}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_WEST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.WEST, NeighborOrientation.DOWN, NeighborOrientation.WEST}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_DOWN, NeighborOrientation.EAST, NeighborOrientation.DOWN, NeighborOrientation.EAST}, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_EAST, NeighborOrientation.FLIP_UP, NeighborOrientation.EAST, NeighborOrientation.UP, NeighborOrientation.EAST}),
|
||||
WEST(new Direction[]{Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH}, 0.6F, true, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.SOUTH, NeighborOrientation.UP, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_UP, NeighborOrientation.SOUTH}, new NeighborOrientation[]{NeighborOrientation.UP, NeighborOrientation.NORTH, NeighborOrientation.UP, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_UP, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.NORTH, NeighborOrientation.DOWN, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_NORTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.DOWN, NeighborOrientation.SOUTH, NeighborOrientation.DOWN, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.SOUTH}),
|
||||
EAST(new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH}, 0.6F, true, new NeighborOrientation[]{NeighborOrientation.FLIP_DOWN, NeighborOrientation.SOUTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.DOWN, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.DOWN, NeighborOrientation.SOUTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_DOWN, NeighborOrientation.NORTH, NeighborOrientation.FLIP_DOWN, NeighborOrientation.FLIP_NORTH, NeighborOrientation.DOWN, NeighborOrientation.FLIP_NORTH, NeighborOrientation.DOWN, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_UP, NeighborOrientation.NORTH, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_NORTH, NeighborOrientation.UP, NeighborOrientation.FLIP_NORTH, NeighborOrientation.UP, NeighborOrientation.NORTH}, new NeighborOrientation[]{NeighborOrientation.FLIP_UP, NeighborOrientation.SOUTH, NeighborOrientation.FLIP_UP, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.UP, NeighborOrientation.FLIP_SOUTH, NeighborOrientation.UP, NeighborOrientation.SOUTH});
|
||||
|
||||
private final Direction[] faces;
|
||||
private final boolean nonCubicWeight;
|
||||
private final NeighborOrientation[] field_4192;
|
||||
private final NeighborOrientation[] field_4185;
|
||||
private final NeighborOrientation[] field_4180;
|
||||
private final NeighborOrientation[] field_4188;
|
||||
private static final NeighborData[] field_4190 = (NeighborData[])SystemUtil.consume(new NeighborData[6], (blockModelRenderer$NeighborDatas_1) -> {
|
||||
blockModelRenderer$NeighborDatas_1[Direction.DOWN.getId()] = DOWN;
|
||||
blockModelRenderer$NeighborDatas_1[Direction.UP.getId()] = UP;
|
||||
blockModelRenderer$NeighborDatas_1[Direction.NORTH.getId()] = NORTH;
|
||||
blockModelRenderer$NeighborDatas_1[Direction.SOUTH.getId()] = SOUTH;
|
||||
blockModelRenderer$NeighborDatas_1[Direction.WEST.getId()] = WEST;
|
||||
blockModelRenderer$NeighborDatas_1[Direction.EAST.getId()] = EAST;
|
||||
});
|
||||
|
||||
private NeighborData(Direction[] directions_1, float float_1, boolean boolean_1, NeighborOrientation[] blockModelRenderer$NeighborOrientations_1, NeighborOrientation[] blockModelRenderer$NeighborOrientations_2, NeighborOrientation[] blockModelRenderer$NeighborOrientations_3, NeighborOrientation[] blockModelRenderer$NeighborOrientations_4) {
|
||||
this.faces = directions_1;
|
||||
this.nonCubicWeight = boolean_1;
|
||||
this.field_4192 = blockModelRenderer$NeighborOrientations_1;
|
||||
this.field_4185 = blockModelRenderer$NeighborOrientations_2;
|
||||
this.field_4180 = blockModelRenderer$NeighborOrientations_3;
|
||||
this.field_4188 = blockModelRenderer$NeighborOrientations_4;
|
||||
}
|
||||
|
||||
public static NeighborData getData(Direction direction_1) {
|
||||
return field_4190[direction_1.getId()];
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static enum NeighborOrientation {
|
||||
DOWN(Direction.DOWN, false),
|
||||
UP(Direction.UP, false),
|
||||
NORTH(Direction.NORTH, false),
|
||||
SOUTH(Direction.SOUTH, false),
|
||||
WEST(Direction.WEST, false),
|
||||
EAST(Direction.EAST, false),
|
||||
FLIP_DOWN(Direction.DOWN, true),
|
||||
FLIP_UP(Direction.UP, true),
|
||||
FLIP_NORTH(Direction.NORTH, true),
|
||||
FLIP_SOUTH(Direction.SOUTH, true),
|
||||
FLIP_WEST(Direction.WEST, true),
|
||||
FLIP_EAST(Direction.EAST, true);
|
||||
|
||||
private final int shape;
|
||||
|
||||
private NeighborOrientation(Direction direction_1, boolean boolean_1) {
|
||||
this.shape = direction_1.getId() + (boolean_1 ? Direction.values().length : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateShape(BlockRenderView extendedBlockView_1, BlockState blockState_1, BlockPos blockPos_1, int[] ints_1, Direction direction_1, float[] floats_1, BitSet bitSet_1) {
|
||||
float float_1 = 32.0F;
|
||||
float float_2 = 32.0F;
|
||||
float float_3 = 32.0F;
|
||||
float float_4 = -32.0F;
|
||||
float float_5 = -32.0F;
|
||||
float float_6 = -32.0F;
|
||||
|
||||
int int_2;
|
||||
float float_11;
|
||||
for(int_2 = 0; int_2 < 4; ++int_2) {
|
||||
float_11 = Float.intBitsToFloat(ints_1[int_2 * 7]);
|
||||
float float_8 = Float.intBitsToFloat(ints_1[int_2 * 7 + 1]);
|
||||
float float_9 = Float.intBitsToFloat(ints_1[int_2 * 7 + 2]);
|
||||
float_1 = Math.min(float_1, float_11);
|
||||
float_2 = Math.min(float_2, float_8);
|
||||
float_3 = Math.min(float_3, float_9);
|
||||
float_4 = Math.max(float_4, float_11);
|
||||
float_5 = Math.max(float_5, float_8);
|
||||
float_6 = Math.max(float_6, float_9);
|
||||
}
|
||||
|
||||
if (floats_1 != null) {
|
||||
floats_1[Direction.WEST.getId()] = float_1;
|
||||
floats_1[Direction.EAST.getId()] = float_4;
|
||||
floats_1[Direction.DOWN.getId()] = float_2;
|
||||
floats_1[Direction.UP.getId()] = float_5;
|
||||
floats_1[Direction.NORTH.getId()] = float_3;
|
||||
floats_1[Direction.SOUTH.getId()] = float_6;
|
||||
int_2 = Direction.values().length;
|
||||
floats_1[Direction.WEST.getId() + int_2] = 1.0F - float_1;
|
||||
floats_1[Direction.EAST.getId() + int_2] = 1.0F - float_4;
|
||||
floats_1[Direction.DOWN.getId() + int_2] = 1.0F - float_2;
|
||||
floats_1[Direction.UP.getId() + int_2] = 1.0F - float_5;
|
||||
floats_1[Direction.NORTH.getId() + int_2] = 1.0F - float_3;
|
||||
floats_1[Direction.SOUTH.getId() + int_2] = 1.0F - float_6;
|
||||
}
|
||||
|
||||
float_11 = 0.9999F;
|
||||
switch(direction_1) {
|
||||
case DOWN:
|
||||
bitSet_1.set(1, float_1 >= 1.0E-4F || float_3 >= 1.0E-4F || float_4 <= 0.9999F || float_6 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_2 < 1.0E-4F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_2 == float_5);
|
||||
break;
|
||||
case UP:
|
||||
bitSet_1.set(1, float_1 >= 1.0E-4F || float_3 >= 1.0E-4F || float_4 <= 0.9999F || float_6 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_5 > 0.9999F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_2 == float_5);
|
||||
break;
|
||||
case NORTH:
|
||||
bitSet_1.set(1, float_1 >= 1.0E-4F || float_2 >= 1.0E-4F || float_4 <= 0.9999F || float_5 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_3 < 1.0E-4F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_3 == float_6);
|
||||
break;
|
||||
case SOUTH:
|
||||
bitSet_1.set(1, float_1 >= 1.0E-4F || float_2 >= 1.0E-4F || float_4 <= 0.9999F || float_5 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_6 > 0.9999F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_3 == float_6);
|
||||
break;
|
||||
case WEST:
|
||||
bitSet_1.set(1, float_2 >= 1.0E-4F || float_3 >= 1.0E-4F || float_5 <= 0.9999F || float_6 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_1 < 1.0E-4F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_1 == float_4);
|
||||
break;
|
||||
case EAST:
|
||||
bitSet_1.set(1, float_2 >= 1.0E-4F || float_3 >= 1.0E-4F || float_5 <= 0.9999F || float_6 <= 0.9999F);
|
||||
bitSet_1.set(0, (float_4 > 0.9999F || Block.isShapeFullCube(blockState_1.getCollisionShape(extendedBlockView_1, blockPos_1))) && float_1 == float_4);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.aocalc;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.BitSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessAmbientOcclusionCalculator;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBlockModelRenderer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.block.BlockModelRenderer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
public class VanillaAoHelper {
|
||||
private static Supplier<AccessAmbientOcclusionCalculator> factory;
|
||||
|
||||
// Renderer method we call isn't declared as static, but uses no
|
||||
// instance data and is called from multiple threads in vanilla also.
|
||||
private static AccessBlockModelRenderer blockRenderer;
|
||||
|
||||
public static void initialize(BlockModelRenderer instance) {
|
||||
blockRenderer = (AccessBlockModelRenderer) instance;
|
||||
|
||||
final String target = FabricLoader.getInstance().getMappingResolver()
|
||||
.mapClassName("intermediary", "net.minecraft.class_778$class_780");
|
||||
|
||||
for (Class<?> innerClass : BlockModelRenderer.class.getDeclaredClasses()) {
|
||||
if (innerClass.getName().equals(target)) {
|
||||
Constructor<?> constructor = innerClass.getDeclaredConstructors()[0];
|
||||
constructor.setAccessible(true);
|
||||
|
||||
factory = new Supplier<AccessAmbientOcclusionCalculator>() {
|
||||
@Override
|
||||
public AccessAmbientOcclusionCalculator get() {
|
||||
try {
|
||||
return (AccessAmbientOcclusionCalculator) constructor.newInstance(instance);
|
||||
} catch (Exception e) {
|
||||
Indigo.LOGGER.warn("[Indigo] Exception accessing vanilla smooth lighter", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (factory != null && factory.get() == null) {
|
||||
factory = null;
|
||||
}
|
||||
|
||||
if (factory == null) {
|
||||
Indigo.LOGGER.warn("[Indigo] Vanilla smooth lighter unavailable. Indigo lighter will be used even if not configured.");
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessAmbientOcclusionCalculator get() {
|
||||
return factory == null ? null : factory.get();
|
||||
}
|
||||
|
||||
public static void updateShape(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
|
||||
blockRenderer.fabric_updateShape(blockRenderView, blockState, pos, vertexData, face, aoData, controlBits);
|
||||
}
|
||||
}
|
|
@ -30,152 +30,150 @@ import net.minecraft.util.math.Direction;
|
|||
* designed to be usable without the default renderer.
|
||||
*/
|
||||
public abstract class ColorHelper {
|
||||
/**
|
||||
* Implement on quads to use methods that require it.
|
||||
* Allows for much cleaner method signatures.
|
||||
*/
|
||||
public static interface ShadeableQuad extends MutableQuadView {
|
||||
boolean isFaceAligned();
|
||||
boolean needsDiffuseShading(int textureIndex);
|
||||
}
|
||||
|
||||
private ColorHelper() {}
|
||||
private ColorHelper() { }
|
||||
|
||||
/**
|
||||
* Implement on quads to use methods that require it.
|
||||
* Allows for much cleaner method signatures.
|
||||
*/
|
||||
public static interface ShadeableQuad extends MutableQuadView {
|
||||
boolean isFaceAligned();
|
||||
|
||||
/** Same as vanilla values */
|
||||
private static final float[] FACE_SHADE_FACTORS = { 0.5F, 1.0F, 0.8F, 0.8F, 0.6F, 0.6F};
|
||||
|
||||
private static final Int2IntFunction colorSwapper = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN
|
||||
? color -> ((color & 0xFF00FF00) | ((color & 0x00FF0000) >> 16) | ((color & 0xFF) << 16))
|
||||
: color -> color;
|
||||
|
||||
/**
|
||||
* Swaps red blue order if needed to match GPU expectations for color component order.
|
||||
*/
|
||||
public static int swapRedBlueIfNeeded(int color) {
|
||||
return colorSwapper.applyAsInt(color);
|
||||
}
|
||||
boolean needsDiffuseShading(int textureIndex);
|
||||
}
|
||||
|
||||
/** Component-wise multiply. Components need to be in same order in both inputs! */
|
||||
public static int multiplyColor(int color1, int color2) {
|
||||
if(color1 == -1) {
|
||||
return color2;
|
||||
} else if (color2 == -1) {
|
||||
return color1;
|
||||
}
|
||||
|
||||
int alpha = ((color1 >> 24) & 0xFF) * ((color2 >> 24) & 0xFF) / 0xFF;
|
||||
int red = ((color1 >> 16) & 0xFF) * ((color2 >> 16) & 0xFF) / 0xFF;
|
||||
int green = ((color1 >> 8) & 0xFF) * ((color2 >> 8) & 0xFF) / 0xFF;
|
||||
int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF;
|
||||
/** Same as vanilla values */
|
||||
private static final float[] FACE_SHADE_FACTORS = { 0.5F, 1.0F, 0.8F, 0.8F, 0.6F, 0.6F };
|
||||
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
private static final Int2IntFunction colorSwapper = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? color -> ((color & 0xFF00FF00) | ((color & 0x00FF0000) >> 16) | ((color & 0xFF) << 16)) : color -> color;
|
||||
|
||||
/** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */
|
||||
public static int multiplyRGB(int color, float shade) {
|
||||
int alpha = ((color >> 24) & 0xFF);
|
||||
int red = (int) (((color >> 16) & 0xFF) * shade);
|
||||
int green = (int) (((color >> 8) & 0xFF) * shade);
|
||||
int blue = (int) ((color & 0xFF) * shade);
|
||||
/**
|
||||
* Swaps red blue order if needed to match GPU expectations for color component order.
|
||||
*/
|
||||
public static int swapRedBlueIfNeeded(int color) {
|
||||
return colorSwapper.applyAsInt(color);
|
||||
}
|
||||
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
/** Component-wise multiply. Components need to be in same order in both inputs! */
|
||||
public static int multiplyColor(int color1, int color2) {
|
||||
if (color1 == -1) {
|
||||
return color2;
|
||||
} else if (color2 == -1) {
|
||||
return color1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same results as {@link BakedQuadFactory#method_3456(Direction)}
|
||||
*/
|
||||
public static float diffuseShade(Direction direction) {
|
||||
return FACE_SHADE_FACTORS[direction.getId()];
|
||||
}
|
||||
int alpha = ((color1 >> 24) & 0xFF) * ((color2 >> 24) & 0xFF) / 0xFF;
|
||||
int red = ((color1 >> 16) & 0xFF) * ((color2 >> 16) & 0xFF) / 0xFF;
|
||||
int green = ((color1 >> 8) & 0xFF) * ((color2 >> 8) & 0xFF) / 0xFF;
|
||||
int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF;
|
||||
|
||||
/**
|
||||
* Formula mimics vanilla lighting for plane-aligned quads and
|
||||
* is vaguely consistent with Phong lighting ambient + diffuse for others.
|
||||
*/
|
||||
public static float normalShade(float normalX, float normalY, float normalZ) {
|
||||
return Math.min(0.5f + Math.abs(normalX) * 0.1f + (normalY > 0 ? 0.5f * normalY : 0) + Math.abs(normalZ) * 0.3f, 1f);
|
||||
}
|
||||
|
||||
public static float normalShade(Vector3f normal) {
|
||||
return normalShade(normal.getX(), normal.getY(), normal.getZ());
|
||||
}
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link diffuseShade}
|
||||
*/
|
||||
public static float vertexShade(ShadeableQuad q, int vertexIndex, float faceShade) {
|
||||
return q.hasNormal(vertexIndex)
|
||||
? normalShade(q.normalX(vertexIndex), q.normalY(vertexIndex), q.normalZ(vertexIndex)) : faceShade;
|
||||
}
|
||||
/** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */
|
||||
public static int multiplyRGB(int color, float shade) {
|
||||
int alpha = ((color >> 24) & 0xFF);
|
||||
int red = (int) (((color >> 16) & 0xFF) * shade);
|
||||
int green = (int) (((color >> 8) & 0xFF) * shade);
|
||||
int blue = (int) ((color & 0xFF) * shade);
|
||||
|
||||
/**
|
||||
* Returns {@link #diffuseShade(Direction)} if quad is aligned to light face,
|
||||
* otherwise uses face normal and {@link #normalShade}
|
||||
*/
|
||||
public static float faceShade(ShadeableQuad quad) {
|
||||
return quad.isFaceAligned() ? diffuseShade(quad.lightFace()) : normalShade(quad.faceNormal());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface VertexLighter {
|
||||
void shade(ShadeableQuad quad, int vertexIndex, float shade);
|
||||
}
|
||||
|
||||
private static VertexLighter[] VERTEX_LIGHTERS = new VertexLighter[8];
|
||||
|
||||
static {
|
||||
VERTEX_LIGHTERS[0b000] = (q, i, s) -> {};
|
||||
VERTEX_LIGHTERS[0b001] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s));
|
||||
VERTEX_LIGHTERS[0b010] = (q, i, s) -> q.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s));
|
||||
VERTEX_LIGHTERS[0b011] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s))
|
||||
.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s));
|
||||
VERTEX_LIGHTERS[0b100] = (q, i, s) -> q.spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b101] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s))
|
||||
.spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b110] = (q, i, s) -> q.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s))
|
||||
.spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b111] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s))
|
||||
.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s))
|
||||
.spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Honors vertex normals and uses non-cubic face normals for non-cubic quads.
|
||||
*
|
||||
* @param quad Quad to be shaded/unshaded.<p>
|
||||
*
|
||||
* @param undo If true, will reverse prior application. Does not check that
|
||||
* prior application actually happened. Use to "unbake" a quad.
|
||||
* Some drift of colors may occur due to floating-point precision error.
|
||||
*/
|
||||
public static void applyDiffuseShading(ShadeableQuad quad, boolean undo) {
|
||||
final float faceShade = faceShade(quad);
|
||||
int i = quad.needsDiffuseShading(0) ? 1 : 0;
|
||||
if(quad.needsDiffuseShading(1)) {
|
||||
i |= 2;
|
||||
}
|
||||
if(quad.needsDiffuseShading(2)) {
|
||||
i |= 4;
|
||||
}
|
||||
if(i == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VertexLighter shader = VERTEX_LIGHTERS[i];
|
||||
for(int j = 0; j < 4; j++) {
|
||||
final float vertexShade = vertexShade(quad, j, faceShade);
|
||||
shader.shade(quad, j, undo ? 1f / vertexShade : vertexShade);
|
||||
}
|
||||
}
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component-wise max
|
||||
*/
|
||||
public static int maxBrightness(int b0, int b1) {
|
||||
if(b0 == 0)
|
||||
return b1;
|
||||
else if (b1 == 0)
|
||||
return b0;
|
||||
return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000);
|
||||
}
|
||||
/**
|
||||
* Same results as {@link BakedQuadFactory#method_3456(Direction)}
|
||||
*/
|
||||
public static float diffuseShade(Direction direction) {
|
||||
return FACE_SHADE_FACTORS[direction.getId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formula mimics vanilla lighting for plane-aligned quads and
|
||||
* is vaguely consistent with Phong lighting ambient + diffuse for others.
|
||||
*/
|
||||
public static float normalShade(float normalX, float normalY, float normalZ) {
|
||||
return Math.min(0.5f + Math.abs(normalX) * 0.1f + (normalY > 0 ? 0.5f * normalY : 0) + Math.abs(normalZ) * 0.3f, 1f);
|
||||
}
|
||||
|
||||
public static float normalShade(Vector3f normal) {
|
||||
return normalShade(normal.getX(), normal.getY(), normal.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link diffuseShade}
|
||||
*/
|
||||
public static float vertexShade(ShadeableQuad q, int vertexIndex, float faceShade) {
|
||||
return q.hasNormal(vertexIndex) ? normalShade(q.normalX(vertexIndex), q.normalY(vertexIndex), q.normalZ(vertexIndex)) : faceShade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #diffuseShade(Direction)} if quad is aligned to light face,
|
||||
* otherwise uses face normal and {@link #normalShade}
|
||||
*/
|
||||
public static float faceShade(ShadeableQuad quad) {
|
||||
return quad.isFaceAligned() ? diffuseShade(quad.lightFace()) : normalShade(quad.faceNormal());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface VertexLighter {
|
||||
void shade(ShadeableQuad quad, int vertexIndex, float shade);
|
||||
}
|
||||
|
||||
private static VertexLighter[] VERTEX_LIGHTERS = new VertexLighter[8];
|
||||
|
||||
static {
|
||||
VERTEX_LIGHTERS[0b000] = (q, i, s) -> {
|
||||
};
|
||||
VERTEX_LIGHTERS[0b001] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s));
|
||||
VERTEX_LIGHTERS[0b010] = (q, i, s) -> q.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s));
|
||||
VERTEX_LIGHTERS[0b011] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s)).spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s));
|
||||
VERTEX_LIGHTERS[0b100] = (q, i, s) -> q.spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b101] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s)).spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b110] = (q, i, s) -> q.spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s)).spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
VERTEX_LIGHTERS[0b111] = (q, i, s) -> q.spriteColor(i, 0, multiplyRGB(q.spriteColor(i, 0), s)).spriteColor(i, 1, multiplyRGB(q.spriteColor(i, 1), s)).spriteColor(i, 2, multiplyRGB(q.spriteColor(i, 2), s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Honors vertex normals and uses non-cubic face normals for non-cubic quads.
|
||||
*
|
||||
* @param quad Quad to be shaded/unshaded.<p>
|
||||
*
|
||||
* @param undo If true, will reverse prior application. Does not check that
|
||||
* prior application actually happened. Use to "unbake" a quad.
|
||||
* Some drift of colors may occur due to floating-point precision error.
|
||||
*/
|
||||
public static void applyDiffuseShading(ShadeableQuad quad, boolean undo) {
|
||||
final float faceShade = faceShade(quad);
|
||||
int i = quad.needsDiffuseShading(0) ? 1 : 0;
|
||||
|
||||
if (quad.needsDiffuseShading(1)) {
|
||||
i |= 2;
|
||||
}
|
||||
|
||||
if (quad.needsDiffuseShading(2)) {
|
||||
i |= 4;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VertexLighter shader = VERTEX_LIGHTERS[i];
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
final float vertexShade = vertexShade(quad, j, faceShade);
|
||||
shader.shade(quad, j, undo ? 1f / vertexShade : vertexShade);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component-wise max
|
||||
*/
|
||||
public static int maxBrightness(int b0, int b1) {
|
||||
if (b0 == 0)
|
||||
return b1;
|
||||
else if (b1 == 0)
|
||||
return b0;
|
||||
return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,215 +31,220 @@ import net.minecraft.util.math.Direction.AxisDirection;
|
|||
* designed to be usable without the default renderer.
|
||||
*/
|
||||
public abstract class GeometryHelper {
|
||||
/** set when a quad touches all four corners of a unit cube */
|
||||
public static final int CUBIC_FLAG = 1;
|
||||
|
||||
/** set when a quad is parallel to (but not necessarily on) a its light face */
|
||||
public static final int AXIS_ALIGNED_FLAG = CUBIC_FLAG << 1;
|
||||
|
||||
/** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */
|
||||
public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1;
|
||||
private GeometryHelper() { }
|
||||
|
||||
/** set when a quad touches all four corners of a unit cube */
|
||||
public static final int CUBIC_FLAG = 1;
|
||||
|
||||
/** set when a quad is parallel to (but not necessarily on) a its light face */
|
||||
public static final int AXIS_ALIGNED_FLAG = CUBIC_FLAG << 1;
|
||||
|
||||
/** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */
|
||||
public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1;
|
||||
|
||||
/** how many bits quad header encoding should reserve for encoding geometry flags */
|
||||
public static final int FLAG_BIT_COUNT = 3;
|
||||
|
||||
private static final float EPS_MIN = 0.0001f;
|
||||
private static final float EPS_MAX = 1.0f - EPS_MIN;
|
||||
|
||||
private GeometryHelper() {}
|
||||
|
||||
/**
|
||||
* Analyzes the quad and returns a value with some combination
|
||||
* of {@link #AXIS_ALIGNED_FLAG}, {@link #LIGHT_FACE_FLAG} and {@link #CUBIC_FLAG}.
|
||||
* Intended use is to optimize lighting when the geometry is regular.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static int computeShapeFlags(QuadView quad) {
|
||||
Direction lightFace = quad.lightFace();
|
||||
int bits = 0;
|
||||
if(isQuadParallelToFace(lightFace, quad)) {
|
||||
bits |= AXIS_ALIGNED_FLAG;
|
||||
if(isParallelQuadOnFace(lightFace, quad)) {
|
||||
bits |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
}
|
||||
if(isQuadCubic(lightFace, quad)) {
|
||||
/**
|
||||
* Analyzes the quad and returns a value with some combination
|
||||
* of {@link #AXIS_ALIGNED_FLAG}, {@link #LIGHT_FACE_FLAG} and {@link #CUBIC_FLAG}.
|
||||
* Intended use is to optimize lighting when the geometry is regular.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static int computeShapeFlags(QuadView quad) {
|
||||
Direction lightFace = quad.lightFace();
|
||||
int bits = 0;
|
||||
|
||||
if (isQuadParallelToFace(lightFace, quad)) {
|
||||
bits |= AXIS_ALIGNED_FLAG;
|
||||
|
||||
if (isParallelQuadOnFace(lightFace, quad)) {
|
||||
bits |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
if (isQuadCubic(lightFace, quad)) {
|
||||
bits |= CUBIC_FLAG;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if quad is parallel to the given face.
|
||||
* Does not validate quad winding order.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static boolean isQuadParallelToFace(Direction face, QuadView quad) {
|
||||
if(face == null) {
|
||||
return false;
|
||||
}
|
||||
int i = face.getAxis().ordinal();
|
||||
final float val = quad.posByIndex(0, i);
|
||||
return approximatelyEquals(val, quad.posByIndex(1, i))
|
||||
&& approximatelyEquals(val, quad.posByIndex(2, i))
|
||||
&& approximatelyEquals(val, quad.posByIndex(3, i));
|
||||
}
|
||||
|
||||
/**
|
||||
* True if quad - already known to be parallel to a face - is actually coplanar with it.
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if quad is parallel to the given face.
|
||||
* Does not validate quad winding order.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static boolean isQuadParallelToFace(Direction face, QuadView quad) {
|
||||
if (face == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int i = face.getAxis().ordinal();
|
||||
final float val = quad.posByIndex(0, i);
|
||||
return approximatelyEquals(val, quad.posByIndex(1, i)) && approximatelyEquals(val, quad.posByIndex(2, i)) && approximatelyEquals(val, quad.posByIndex(3, i));
|
||||
}
|
||||
|
||||
/**
|
||||
* True if quad - already known to be parallel to a face - is actually coplanar with it.
|
||||
* For compatibility with vanilla resource packs, also true if quad is outside the face.<p>
|
||||
*
|
||||
* Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)}
|
||||
* for that purpose. Expects convex quads with all points co-planar.<p>
|
||||
*/
|
||||
public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) {
|
||||
if(lightFace == null)
|
||||
return false;
|
||||
final float x = quad.posByIndex(0, lightFace.getAxis().ordinal());
|
||||
return lightFace.getDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if quad is truly a quad (not a triangle) and fills a full block cross-section.
|
||||
* If known to be true, allows use of a simpler/faster AO lighting algorithm.<p>
|
||||
*
|
||||
* Does not check if quad is actually coplanar with the light face, nor does it check that all
|
||||
* quad vertices are coplanar with each other. <p>
|
||||
*
|
||||
* Expects convex quads with all points co-planar.<p>
|
||||
*
|
||||
* @param lightFace MUST be non-null.
|
||||
*/
|
||||
public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
|
||||
if(lightFace == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int a, b;
|
||||
*
|
||||
* Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)}
|
||||
* for that purpose. Expects convex quads with all points co-planar.<p>
|
||||
*/
|
||||
public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) {
|
||||
if (lightFace == null) return false;
|
||||
|
||||
final float x = quad.posByIndex(0, lightFace.getAxis().ordinal());
|
||||
return lightFace.getDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN;
|
||||
}
|
||||
|
||||
switch(lightFace) {
|
||||
case EAST:
|
||||
case WEST:
|
||||
a = 1;
|
||||
b = 2;
|
||||
break;
|
||||
case UP:
|
||||
case DOWN:
|
||||
a = 0;
|
||||
b = 2;
|
||||
break;
|
||||
case SOUTH:
|
||||
case NORTH:
|
||||
a = 1;
|
||||
b = 0;
|
||||
break;
|
||||
default:
|
||||
// handle WTF case
|
||||
return false;
|
||||
}
|
||||
|
||||
return confirmSquareCorners(a, b, quad);
|
||||
}
|
||||
/**
|
||||
* Returns true if quad is truly a quad (not a triangle) and fills a full block cross-section.
|
||||
* If known to be true, allows use of a simpler/faster AO lighting algorithm.<p>
|
||||
*
|
||||
* Does not check if quad is actually coplanar with the light face, nor does it check that all
|
||||
* quad vertices are coplanar with each other. <p>
|
||||
*
|
||||
* Expects convex quads with all points co-planar.<p>
|
||||
*
|
||||
* @param lightFace MUST be non-null.
|
||||
*/
|
||||
public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
|
||||
if (lightFace == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link #isQuadCubic(Direction, QuadView)}.
|
||||
* True if quad touches all four corners of unit square.<p>
|
||||
int a, b;
|
||||
|
||||
switch (lightFace) {
|
||||
case EAST:
|
||||
case WEST:
|
||||
a = 1;
|
||||
b = 2;
|
||||
break;
|
||||
case UP:
|
||||
case DOWN:
|
||||
a = 0;
|
||||
b = 2;
|
||||
break;
|
||||
case SOUTH:
|
||||
case NORTH:
|
||||
a = 1;
|
||||
b = 0;
|
||||
break;
|
||||
default:
|
||||
// handle WTF case
|
||||
return false;
|
||||
}
|
||||
|
||||
return confirmSquareCorners(a, b, quad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link #isQuadCubic(Direction, QuadView)}.
|
||||
* True if quad touches all four corners of unit square.<p>
|
||||
*
|
||||
* For compatibility with resource packs that contain models with quads exceeding
|
||||
* block boundaries, considers corners outside the block to be at the corners.
|
||||
*/
|
||||
private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) {
|
||||
int flags = 0;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
final float a = quad.posByIndex(i, aCoordinate);
|
||||
final float b = quad.posByIndex(i, bCoordinate);
|
||||
|
||||
if(a <= EPS_MIN) {
|
||||
if(b <= EPS_MIN) {
|
||||
flags |= 1;
|
||||
} else if(b >= EPS_MAX) {
|
||||
flags |= 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if(a >= EPS_MAX) {
|
||||
if(b <= EPS_MIN) {
|
||||
flags |= 4;
|
||||
} else if(b >= EPS_MAX) {
|
||||
flags |= 8;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return flags == 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the face to which the quad is most closely aligned.
|
||||
* This mimics the value that {@link BakedQuad#getFace()} returns, and is
|
||||
* used in the vanilla renderer for all diffuse lighting.<p>
|
||||
*
|
||||
* Derived from the quad face normal and expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static Direction lightFace(QuadView quad) {
|
||||
final Vector3f normal = quad.faceNormal();
|
||||
switch(GeometryHelper.longestAxis(normal)) {
|
||||
case X:
|
||||
return normal.getX() > 0 ? Direction.EAST : Direction.WEST;
|
||||
|
||||
case Y:
|
||||
return normal.getY() > 0 ? Direction.UP : Direction.DOWN;
|
||||
|
||||
case Z:
|
||||
return normal.getZ() > 0 ? Direction.SOUTH : Direction.NORTH;
|
||||
|
||||
default:
|
||||
// handle WTF case
|
||||
return Direction.UP;
|
||||
}
|
||||
}
|
||||
*/
|
||||
private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) {
|
||||
int flags = 0;
|
||||
|
||||
/**
|
||||
* Simple 4-way compare, doesn't handle NaN values.
|
||||
*/
|
||||
public static float min(float a, float b, float c, float d) {
|
||||
final float x = a < b ? a : b;
|
||||
final float y = c < d ? c : d;
|
||||
return x < y ? x : y;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final float a = quad.posByIndex(i, aCoordinate);
|
||||
final float b = quad.posByIndex(i, bCoordinate);
|
||||
|
||||
/**
|
||||
* Simple 4-way compare, doesn't handle NaN values.
|
||||
*/
|
||||
public static float max(float a, float b, float c, float d) {
|
||||
final float x = a > b ? a : b;
|
||||
final float y = c > d ? c : d;
|
||||
return x > y ? x : y;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #longestAxis(float, float, float)}
|
||||
*/
|
||||
public static Axis longestAxis(Vector3f vec) {
|
||||
return longestAxis(vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the largest (max absolute magnitude) component (X, Y, Z) in the given vector.
|
||||
*/
|
||||
public static Axis longestAxis(float normalX, float normalY, float normalZ) {
|
||||
Axis result = Axis.Y;
|
||||
float longest = Math.abs(normalY);
|
||||
|
||||
float a = Math.abs(normalX);
|
||||
if(a > longest)
|
||||
{
|
||||
result = Axis.X;
|
||||
longest = a;
|
||||
}
|
||||
|
||||
return Math.abs(normalZ) > longest
|
||||
? Axis.Z : result;
|
||||
}
|
||||
if (a <= EPS_MIN) {
|
||||
if (b <= EPS_MIN) {
|
||||
flags |= 1;
|
||||
} else if (b >= EPS_MAX) {
|
||||
flags |= 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (a >= EPS_MAX) {
|
||||
if (b <= EPS_MIN) {
|
||||
flags |= 4;
|
||||
} else if (b >= EPS_MAX) {
|
||||
flags |= 8;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return flags == 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the face to which the quad is most closely aligned.
|
||||
* This mimics the value that {@link BakedQuad#getFace()} returns, and is
|
||||
* used in the vanilla renderer for all diffuse lighting.<p>
|
||||
*
|
||||
* Derived from the quad face normal and expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static Direction lightFace(QuadView quad) {
|
||||
final Vector3f normal = quad.faceNormal();
|
||||
switch (GeometryHelper.longestAxis(normal)) {
|
||||
case X:
|
||||
return normal.getX() > 0 ? Direction.EAST : Direction.WEST;
|
||||
|
||||
case Y:
|
||||
return normal.getY() > 0 ? Direction.UP : Direction.DOWN;
|
||||
|
||||
case Z:
|
||||
return normal.getZ() > 0 ? Direction.SOUTH : Direction.NORTH;
|
||||
|
||||
default:
|
||||
// handle WTF case
|
||||
return Direction.UP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple 4-way compare, doesn't handle NaN values.
|
||||
*/
|
||||
public static float min(float a, float b, float c, float d) {
|
||||
final float x = a < b ? a : b;
|
||||
final float y = c < d ? c : d;
|
||||
return x < y ? x : y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple 4-way compare, doesn't handle NaN values.
|
||||
*/
|
||||
public static float max(float a, float b, float c, float d) {
|
||||
final float x = a > b ? a : b;
|
||||
final float y = c > d ? c : d;
|
||||
return x > y ? x : y;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #longestAxis(float, float, float)}
|
||||
*/
|
||||
public static Axis longestAxis(Vector3f vec) {
|
||||
return longestAxis(vec.getX(), vec.getY(), vec.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the largest (max absolute magnitude) component (X, Y, Z) in the given vector.
|
||||
*/
|
||||
public static Axis longestAxis(float normalX, float normalY, float normalZ) {
|
||||
Axis result = Axis.Y;
|
||||
float longest = Math.abs(normalY);
|
||||
float a = Math.abs(normalX);
|
||||
|
||||
if (a > longest) {
|
||||
result = Axis.X;
|
||||
longest = a;
|
||||
}
|
||||
|
||||
return Math.abs(normalZ) > longest ? Axis.Z : result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,87 +28,85 @@ import net.minecraft.util.math.Vec3i;
|
|||
* designed to be usable without the default renderer.
|
||||
*/
|
||||
public abstract class NormalHelper {
|
||||
private NormalHelper() {}
|
||||
private NormalHelper() { }
|
||||
|
||||
/**
|
||||
* Stores a normal plus an extra value as a quartet of signed bytes.
|
||||
* This is the same normal format that vanilla item rendering expects.
|
||||
* The extra value is for use by shaders.
|
||||
*/
|
||||
public static int packNormal(float x, float y, float z, float w) {
|
||||
x = MathHelper.clamp(x, -1, 1);
|
||||
y = MathHelper.clamp(y, -1, 1);
|
||||
z = MathHelper.clamp(z, -1, 1);
|
||||
w = MathHelper.clamp(w, -1, 1);
|
||||
|
||||
return ((int)(x * 127) & 255)
|
||||
| (((int)(y * 127) & 255) << 8)
|
||||
| (((int)(z * 127) & 255) << 16)
|
||||
| (((int)(w * 127) & 255) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of {@link #packNormal(float, float, float, float)} that accepts a vector type.
|
||||
*/
|
||||
public static int packNormal(Vector3f normal, float w) {
|
||||
return packNormal(normal.getX(), normal.getY(), normal.getZ(), w);
|
||||
}
|
||||
/**
|
||||
* Stores a normal plus an extra value as a quartet of signed bytes.
|
||||
* This is the same normal format that vanilla item rendering expects.
|
||||
* The extra value is for use by shaders.
|
||||
*/
|
||||
public static int packNormal(float x, float y, float z, float w) {
|
||||
x = MathHelper.clamp(x, -1, 1);
|
||||
y = MathHelper.clamp(y, -1, 1);
|
||||
z = MathHelper.clamp(z, -1, 1);
|
||||
w = MathHelper.clamp(w, -1, 1);
|
||||
|
||||
/**
|
||||
* Retrieves values packed by {@link #packNormal(float, float, float, float)}
|
||||
* Components are x, y, z, w - zero based
|
||||
*/
|
||||
public static float getPackedNormalComponent(int packedNormal, int component) {
|
||||
return ((float)(byte)(packedNormal >> (8 * component))) / 127f;
|
||||
}
|
||||
return ((int) (x * 127) & 255) | (((int) (y * 127) & 255) << 8) | (((int) (z * 127) & 255) << 16) | (((int) (w * 127) & 255) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the face normal of the given quad and saves it in the provided non-null vector.
|
||||
* If {@link QuadView#nominalFace()} is set will optimize by confirming quad is parallel to that
|
||||
* face and, if so, use the standard normal for that face direction.<p>
|
||||
*
|
||||
* Will work with triangles also. Assumes counter-clockwise winding order, which is the norm.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static void computeFaceNormal(Vector3f saveTo, QuadView q) {
|
||||
final Direction nominalFace = q.nominalFace();
|
||||
if(GeometryHelper.isQuadParallelToFace(nominalFace, q)) {
|
||||
Vec3i vec = nominalFace.getVector();
|
||||
saveTo.set(vec.getX(), vec.getY(), vec.getZ());
|
||||
return;
|
||||
}
|
||||
|
||||
final float x0 = q.x(0);
|
||||
final float y0 = q.y(0);
|
||||
final float z0 = q.z(0);
|
||||
final float x1 = q.x(1);
|
||||
final float y1 = q.y(1);
|
||||
final float z1 = q.z(1);
|
||||
final float x2 = q.x(2);
|
||||
final float y2 = q.y(2);
|
||||
final float z2 = q.z(2);
|
||||
final float x3 = q.x(3);
|
||||
final float y3 = q.y(3);
|
||||
final float z3 = q.z(3);
|
||||
|
||||
final float dx0 = x2 - x0;
|
||||
final float dy0 = y2 - y0;
|
||||
final float dz0 = z2 - z0;
|
||||
final float dx1 = x3 - x1;
|
||||
final float dy1 = y3 - y1;
|
||||
final float dz1 = z3 - z1;
|
||||
|
||||
float normX = dy0 * dz1 - dz0 * dy1;
|
||||
float normY = dz0 * dx1 - dx0 * dz1;
|
||||
float normZ = dx0 * dy1 - dy0 * dx1;
|
||||
|
||||
float l = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ);
|
||||
if(l != 0) {
|
||||
normX /= l;
|
||||
normY /= l;
|
||||
normZ /= l;
|
||||
}
|
||||
|
||||
saveTo.set(normX, normY, normZ);
|
||||
}
|
||||
/**
|
||||
* Version of {@link #packNormal(float, float, float, float)} that accepts a vector type.
|
||||
*/
|
||||
public static int packNormal(Vector3f normal, float w) {
|
||||
return packNormal(normal.getX(), normal.getY(), normal.getZ(), w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves values packed by {@link #packNormal(float, float, float, float)}
|
||||
* Components are x, y, z, w - zero based
|
||||
*/
|
||||
public static float getPackedNormalComponent(int packedNormal, int component) {
|
||||
return ((float) (byte) (packedNormal >> (8 * component))) / 127f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the face normal of the given quad and saves it in the provided non-null vector.
|
||||
* If {@link QuadView#nominalFace()} is set will optimize by confirming quad is parallel to that
|
||||
* face and, if so, use the standard normal for that face direction.<p>
|
||||
*
|
||||
* Will work with triangles also. Assumes counter-clockwise winding order, which is the norm.
|
||||
* Expects convex quads with all points co-planar.
|
||||
*/
|
||||
public static void computeFaceNormal(Vector3f saveTo, QuadView q) {
|
||||
final Direction nominalFace = q.nominalFace();
|
||||
|
||||
if (GeometryHelper.isQuadParallelToFace(nominalFace, q)) {
|
||||
Vec3i vec = nominalFace.getVector();
|
||||
saveTo.set(vec.getX(), vec.getY(), vec.getZ());
|
||||
return;
|
||||
}
|
||||
|
||||
final float x0 = q.x(0);
|
||||
final float y0 = q.y(0);
|
||||
final float z0 = q.z(0);
|
||||
final float x1 = q.x(1);
|
||||
final float y1 = q.y(1);
|
||||
final float z1 = q.z(1);
|
||||
final float x2 = q.x(2);
|
||||
final float y2 = q.y(2);
|
||||
final float z2 = q.z(2);
|
||||
final float x3 = q.x(3);
|
||||
final float y3 = q.y(3);
|
||||
final float z3 = q.z(3);
|
||||
|
||||
final float dx0 = x2 - x0;
|
||||
final float dy0 = y2 - y0;
|
||||
final float dz0 = z2 - z0;
|
||||
final float dx1 = x3 - x1;
|
||||
final float dy1 = y3 - y1;
|
||||
final float dz1 = z3 - z1;
|
||||
|
||||
float normX = dy0 * dz1 - dz0 * dy1;
|
||||
float normY = dz0 * dx1 - dx0 * dz1;
|
||||
float normZ = dx0 * dy1 - dy0 * dx1;
|
||||
float l = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ);
|
||||
|
||||
if (l != 0) {
|
||||
normX /= l;
|
||||
normY /= l;
|
||||
normZ /= l;
|
||||
}
|
||||
|
||||
saveTo.set(normX, normY, normZ);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,81 +26,83 @@ import net.minecraft.util.math.Direction;
|
|||
* itself to implement automatic block-breaking models for enhanced models.
|
||||
*/
|
||||
public class TextureHelper {
|
||||
private static final float NORMALIZER = 1f / 16f;
|
||||
|
||||
/**
|
||||
* Bakes textures in the provided vertex data, handling UV locking,
|
||||
* rotation, interpolation, etc. Textures must not be already baked.
|
||||
*/
|
||||
public static void bakeSprite(MutableQuadView quad, int spriteIndex, Sprite sprite, int bakeFlags) {
|
||||
if(quad.nominalFace() != null && (MutableQuadView.BAKE_LOCK_UV & bakeFlags) != 0) {
|
||||
// Assigns normalized UV coordinates based on vertex positions
|
||||
applyModifier(quad, spriteIndex, UVLOCKERS[quad.nominalFace().getId()]);
|
||||
} else if ((MutableQuadView.BAKE_NORMALIZED & bakeFlags) == 0) {
|
||||
// Scales from 0-16 to 0-1
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, q.spriteU(i, t) * NORMALIZER, q.spriteV(i, t) * NORMALIZER));
|
||||
}
|
||||
|
||||
final int rotation = bakeFlags & 3;
|
||||
if(rotation != 0) {
|
||||
// Rotates texture around the center of sprite.
|
||||
// Assumes normalized coordinates.
|
||||
applyModifier(quad, spriteIndex, ROTATIONS[rotation]);
|
||||
}
|
||||
|
||||
if((MutableQuadView.BAKE_FLIP_U & bakeFlags) != 0) {
|
||||
// Inverts U coordinates. Assumes normalized (0-1) values.
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, 1 - q.spriteU(i, t), q.spriteV(i, t)));
|
||||
}
|
||||
|
||||
if((MutableQuadView.BAKE_FLIP_V & bakeFlags) != 0) {
|
||||
// Inverts V coordinates. Assumes normalized (0-1) values.
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, q.spriteU(i, t), 1 - q.spriteV(i, t)));
|
||||
}
|
||||
|
||||
interpolate(quad, spriteIndex, sprite);
|
||||
}
|
||||
private TextureHelper() { }
|
||||
|
||||
private static final float NORMALIZER = 1f / 16f;
|
||||
|
||||
/**
|
||||
* Faster than sprite method. Sprite computes span and normalizes inputs each call,
|
||||
* so we'd have to denormalize before we called, only to have the sprite renormalize immediately.
|
||||
*/
|
||||
private static void interpolate(MutableQuadView q, int spriteIndex, Sprite sprite) {
|
||||
final float uMin = sprite.getMinU();
|
||||
final float uSpan = sprite.getMaxU() - uMin;
|
||||
final float vMin = sprite.getMinV();
|
||||
final float vSpan = sprite.getMaxV() - vMin;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.sprite(i, spriteIndex, uMin + q.spriteU(i, spriteIndex) * uSpan, vMin + q.spriteV(i, spriteIndex) * vSpan);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface VertexModifier {
|
||||
void apply(MutableQuadView quad, int vertexIndex, int spriteIndex);
|
||||
}
|
||||
|
||||
private static void applyModifier(MutableQuadView quad, int spriteIndex, VertexModifier modifier) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
modifier.apply(quad, i, spriteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static final VertexModifier [] ROTATIONS = new VertexModifier[] {
|
||||
null,
|
||||
(q, i, t) -> q.sprite(i, t, q.spriteV(i, t), q.spriteU(i, t)), //90
|
||||
(q, i, t) -> q.sprite(i, t, 1 - q.spriteU(i, t), 1 - q.spriteV(i, t)), //180
|
||||
(q, i, t) -> q.sprite(i, t, 1 - q.spriteV(i, t), q.spriteU(i, t)) // 270
|
||||
};
|
||||
|
||||
private static final VertexModifier [] UVLOCKERS = new VertexModifier[6];
|
||||
/**
|
||||
* Bakes textures in the provided vertex data, handling UV locking,
|
||||
* rotation, interpolation, etc. Textures must not be already baked.
|
||||
*/
|
||||
public static void bakeSprite(MutableQuadView quad, int spriteIndex, Sprite sprite, int bakeFlags) {
|
||||
if (quad.nominalFace() != null && (MutableQuadView.BAKE_LOCK_UV & bakeFlags) != 0) {
|
||||
// Assigns normalized UV coordinates based on vertex positions
|
||||
applyModifier(quad, spriteIndex, UVLOCKERS[quad.nominalFace().getId()]);
|
||||
} else if ((MutableQuadView.BAKE_NORMALIZED & bakeFlags) == 0) {
|
||||
// Scales from 0-16 to 0-1
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, q.spriteU(i, t) * NORMALIZER, q.spriteV(i, t) * NORMALIZER));
|
||||
}
|
||||
|
||||
static {
|
||||
UVLOCKERS[Direction.EAST.getId()] = (q, i, t) -> q.sprite(i, t, 1 - q.z(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.WEST.getId()] = (q, i, t) -> q.sprite(i, t, q.z(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.NORTH.getId()] = (q, i, t) -> q.sprite(i, t, 1 - q.x(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.SOUTH.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.DOWN.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.z(i));
|
||||
UVLOCKERS[Direction.UP.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.z(i));
|
||||
}
|
||||
final int rotation = bakeFlags & 3;
|
||||
|
||||
if (rotation != 0) {
|
||||
// Rotates texture around the center of sprite.
|
||||
// Assumes normalized coordinates.
|
||||
applyModifier(quad, spriteIndex, ROTATIONS[rotation]);
|
||||
}
|
||||
|
||||
if ((MutableQuadView.BAKE_FLIP_U & bakeFlags) != 0) {
|
||||
// Inverts U coordinates. Assumes normalized (0-1) values.
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, 1 - q.spriteU(i, t), q.spriteV(i, t)));
|
||||
}
|
||||
|
||||
if ((MutableQuadView.BAKE_FLIP_V & bakeFlags) != 0) {
|
||||
// Inverts V coordinates. Assumes normalized (0-1) values.
|
||||
applyModifier(quad, spriteIndex, (q, i, t) -> q.sprite(i, t, q.spriteU(i, t), 1 - q.spriteV(i, t)));
|
||||
}
|
||||
|
||||
interpolate(quad, spriteIndex, sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Faster than sprite method. Sprite computes span and normalizes inputs each call,
|
||||
* so we'd have to denormalize before we called, only to have the sprite renormalize immediately.
|
||||
*/
|
||||
private static void interpolate(MutableQuadView q, int spriteIndex, Sprite sprite) {
|
||||
final float uMin = sprite.getMinU();
|
||||
final float uSpan = sprite.getMaxU() - uMin;
|
||||
final float vMin = sprite.getMinV();
|
||||
final float vSpan = sprite.getMaxV() - vMin;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.sprite(i, spriteIndex, uMin + q.spriteU(i, spriteIndex) * uSpan, vMin + q.spriteV(i, spriteIndex) * vSpan);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface VertexModifier {
|
||||
void apply(MutableQuadView quad, int vertexIndex, int spriteIndex);
|
||||
}
|
||||
|
||||
private static void applyModifier(MutableQuadView quad, int spriteIndex, VertexModifier modifier) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
modifier.apply(quad, i, spriteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static final VertexModifier[] ROTATIONS = new VertexModifier[] { null, (q, i, t) -> q.sprite(i, t, q.spriteV(i, t), q.spriteU(i, t)), //90
|
||||
(q, i, t) -> q.sprite(i, t, 1 - q.spriteU(i, t), 1 - q.spriteV(i, t)), //180
|
||||
(q, i, t) -> q.sprite(i, t, 1 - q.spriteV(i, t), q.spriteU(i, t)) // 270
|
||||
};
|
||||
|
||||
private static final VertexModifier[] UVLOCKERS = new VertexModifier[6];
|
||||
|
||||
static {
|
||||
UVLOCKERS[Direction.EAST.getId()] = (q, i, t) -> q.sprite(i, t, 1 - q.z(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.WEST.getId()] = (q, i, t) -> q.sprite(i, t, q.z(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.NORTH.getId()] = (q, i, t) -> q.sprite(i, t, 1 - q.x(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.SOUTH.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.y(i));
|
||||
UVLOCKERS[Direction.DOWN.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.z(i));
|
||||
UVLOCKERS[Direction.UP.getId()] = (q, i, t) -> q.sprite(i, t, q.x(i), 1 - q.z(i));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,16 @@
|
|||
|
||||
package net.fabricmc.indigo.renderer.mesh;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
|
||||
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
|
||||
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
|
||||
import net.minecraft.client.render.VertexFormat;
|
||||
import net.minecraft.client.render.VertexFormats;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
|
||||
/**
|
||||
* Holds all the array offsets and bit-wise encoders/decoders for
|
||||
|
@ -26,74 +33,109 @@ import net.minecraft.util.math.Direction;
|
|||
* All of this is implementation-specific - that's why it isn't a "helper" class.
|
||||
*/
|
||||
public abstract class EncodingFormat {
|
||||
private EncodingFormat() {}
|
||||
|
||||
static final int HEADER_MATERIAL = 0;
|
||||
static final int HEADER_COLOR_INDEX = 1;
|
||||
static final int HEADER_BITS = 2;
|
||||
static final int HEADER_TAG = 3;
|
||||
public static final int HEADER_STRIDE = 4;
|
||||
|
||||
// our internal format always include packed normals
|
||||
public static final int VERTEX_START_OFFSET = HEADER_STRIDE;
|
||||
static final int VANILLA_STRIDE = 32;
|
||||
// normals are followed by 0-2 sets of color/uv coordinates
|
||||
static final int TEXTURE_STRIDE = 12;
|
||||
/** is one tex stride less than the actual base, because when used tex index is >= 1 */
|
||||
static final int TEXTURE_OFFSET_MINUS = HEADER_STRIDE + VANILLA_STRIDE - TEXTURE_STRIDE;
|
||||
static final int SECOND_TEXTURE_OFFSET = TEXTURE_OFFSET_MINUS + TEXTURE_STRIDE;
|
||||
static final int THIRD_TEXTURE_OFFSET = SECOND_TEXTURE_OFFSET + TEXTURE_STRIDE;
|
||||
public static final int MAX_STRIDE = HEADER_STRIDE + VANILLA_STRIDE
|
||||
+ TEXTURE_STRIDE * (RenderMaterialImpl.MAX_SPRITE_DEPTH - 1);
|
||||
|
||||
/** used for quick clearing of quad buffers */
|
||||
static final int[] EMPTY = new int[MAX_STRIDE];
|
||||
|
||||
private static final int DIRECTION_MASK = 7;
|
||||
private static final int CULL_SHIFT = 0;
|
||||
private static final int CULL_INVERSE_MASK = ~(DIRECTION_MASK << CULL_SHIFT);
|
||||
private static final int LIGHT_SHIFT = CULL_SHIFT + Integer.bitCount(DIRECTION_MASK);
|
||||
private static final int LIGHT_INVERSE_MASK = ~(DIRECTION_MASK << LIGHT_SHIFT);
|
||||
private static final int NORMALS_SHIFT = LIGHT_SHIFT + Integer.bitCount(DIRECTION_MASK);
|
||||
private static final int NORMALS_MASK = 0b1111;
|
||||
private static final int NORMALS_INVERSE_MASK = ~(NORMALS_MASK << NORMALS_SHIFT);
|
||||
private static final int GEOMETRY_SHIFT = NORMALS_SHIFT + Integer.bitCount(NORMALS_MASK);
|
||||
private static final int GEOMETRY_MASK = 0b111;
|
||||
private static final int GEOMETRY_INVERSE_MASK = ~(GEOMETRY_MASK << GEOMETRY_SHIFT);
|
||||
|
||||
static Direction cullFace(int bits) {
|
||||
return ModelHelper.faceFromIndex((bits >> CULL_SHIFT) & DIRECTION_MASK);
|
||||
}
|
||||
|
||||
static int cullFace(int bits, Direction face) {
|
||||
return (bits & CULL_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << CULL_SHIFT);
|
||||
}
|
||||
|
||||
static Direction lightFace(int bits) {
|
||||
return ModelHelper.faceFromIndex((bits >> LIGHT_SHIFT) & DIRECTION_MASK);
|
||||
}
|
||||
|
||||
static int lightFace(int bits, Direction face) {
|
||||
return (bits & LIGHT_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << LIGHT_SHIFT);
|
||||
}
|
||||
|
||||
static int normalFlags(int bits) {
|
||||
return (bits >> NORMALS_SHIFT) & NORMALS_MASK;
|
||||
}
|
||||
|
||||
static int normalFlags(int bits, int normalFlags) {
|
||||
return (bits & NORMALS_INVERSE_MASK) | ((normalFlags & NORMALS_MASK) << NORMALS_SHIFT);
|
||||
}
|
||||
|
||||
public static int stride(int textureDepth) {
|
||||
return SECOND_TEXTURE_OFFSET - TEXTURE_STRIDE + textureDepth * TEXTURE_STRIDE;
|
||||
}
|
||||
|
||||
static int geometryFlags(int bits) {
|
||||
return bits >> GEOMETRY_SHIFT;
|
||||
}
|
||||
|
||||
static int geometryFlags(int bits, int geometryFlags) {
|
||||
return (bits & GEOMETRY_INVERSE_MASK) | ((geometryFlags & GEOMETRY_MASK) << GEOMETRY_SHIFT);
|
||||
}
|
||||
private EncodingFormat() { }
|
||||
|
||||
static final int HEADER_BITS = 0;
|
||||
static final int HEADER_COLOR_INDEX = 1;
|
||||
static final int HEADER_TAG = 2;
|
||||
public static final int HEADER_STRIDE = 3;
|
||||
|
||||
static final int VERTEX_X;
|
||||
static final int VERTEX_Y;
|
||||
static final int VERTEX_Z;
|
||||
static final int VERTEX_COLOR;
|
||||
static final int VERTEX_U;
|
||||
static final int VERTEX_V;
|
||||
static final int VERTEX_LIGHTMAP;
|
||||
static final int VERTEX_NORMAL;
|
||||
public static final int VERTEX_STRIDE;
|
||||
|
||||
public static final int QUAD_STRIDE;
|
||||
public static final int QUAD_STRIDE_BYTES;
|
||||
public static final int TOTAL_STRIDE;
|
||||
|
||||
static {
|
||||
final VertexFormat format = VertexFormats.POSITION_COLOR_UV_NORMAL;
|
||||
VERTEX_X = HEADER_STRIDE + 0;
|
||||
VERTEX_Y = HEADER_STRIDE + 1;
|
||||
VERTEX_Z = HEADER_STRIDE + 2;
|
||||
VERTEX_COLOR = HEADER_STRIDE + (format.getColorOffset() >> 2);
|
||||
VERTEX_U = HEADER_STRIDE + (format.getUvOffset(0) >> 2);
|
||||
VERTEX_V = VERTEX_U + 1;
|
||||
VERTEX_LIGHTMAP = HEADER_STRIDE + (format.getUvOffset(1) >> 2);
|
||||
VERTEX_NORMAL = HEADER_STRIDE + (format.getNormalOffset() >> 2);
|
||||
VERTEX_STRIDE = format.getVertexSizeInteger();
|
||||
QUAD_STRIDE = VERTEX_STRIDE * 4;
|
||||
QUAD_STRIDE_BYTES = QUAD_STRIDE * 4;
|
||||
TOTAL_STRIDE = HEADER_STRIDE + QUAD_STRIDE;
|
||||
|
||||
Preconditions.checkState(VERTEX_STRIDE == QuadView.VANILLA_VERTEX_STRIDE, "Indigo vertex stride (%s) mismatched with rendering API (%s)", VERTEX_STRIDE, QuadView.VANILLA_VERTEX_STRIDE);
|
||||
Preconditions.checkState(QUAD_STRIDE == QuadView.VANILLA_QUAD_STRIDE, "Indigo quad stride (%s) mismatched with rendering API (%s)", QUAD_STRIDE, QuadView.VANILLA_QUAD_STRIDE);
|
||||
}
|
||||
|
||||
/** used for quick clearing of quad buffers */
|
||||
static final int[] EMPTY = new int[TOTAL_STRIDE];
|
||||
|
||||
private static final int DIRECTION_MASK = MathHelper.smallestEncompassingPowerOfTwo(ModelHelper.NULL_FACE_ID) - 1;
|
||||
private static final int DIRECTION_BIT_COUNT = Integer.bitCount(DIRECTION_MASK);
|
||||
private static final int CULL_SHIFT = 0;
|
||||
private static final int CULL_INVERSE_MASK = ~(DIRECTION_MASK << CULL_SHIFT);
|
||||
private static final int LIGHT_SHIFT = CULL_SHIFT + DIRECTION_BIT_COUNT;
|
||||
private static final int LIGHT_INVERSE_MASK = ~(DIRECTION_MASK << LIGHT_SHIFT);
|
||||
private static final int NORMALS_SHIFT = LIGHT_SHIFT + DIRECTION_BIT_COUNT;
|
||||
private static final int NORMALS_COUNT = 4;
|
||||
private static final int NORMALS_MASK = (1 << NORMALS_COUNT) - 1;
|
||||
private static final int NORMALS_INVERSE_MASK = ~(NORMALS_MASK << NORMALS_SHIFT);
|
||||
private static final int GEOMETRY_SHIFT = NORMALS_SHIFT + NORMALS_COUNT;
|
||||
private static final int GEOMETRY_MASK = (1 << GeometryHelper.FLAG_BIT_COUNT) - 1;
|
||||
private static final int GEOMETRY_INVERSE_MASK = ~(GEOMETRY_MASK << GEOMETRY_SHIFT);
|
||||
private static final int MATERIAL_SHIFT = GEOMETRY_SHIFT + GeometryHelper.FLAG_BIT_COUNT;
|
||||
private static final int MATERIAL_MASK = MathHelper.smallestEncompassingPowerOfTwo(RenderMaterialImpl.VALUE_COUNT) - 1;
|
||||
private static final int MATERIAL_BIT_COUNT = Integer.bitCount(MATERIAL_MASK);
|
||||
private static final int MATERIAL_INVERSE_MASK = ~(MATERIAL_MASK << MATERIAL_SHIFT);
|
||||
|
||||
static {
|
||||
Preconditions.checkArgument(MATERIAL_SHIFT + MATERIAL_BIT_COUNT <= 32, "Indigo header encoding bit count (%s) exceeds integer bit length)", TOTAL_STRIDE);
|
||||
}
|
||||
|
||||
static Direction cullFace(int bits) {
|
||||
return ModelHelper.faceFromIndex((bits >> CULL_SHIFT) & DIRECTION_MASK);
|
||||
}
|
||||
|
||||
static int cullFace(int bits, Direction face) {
|
||||
return (bits & CULL_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << CULL_SHIFT);
|
||||
}
|
||||
|
||||
static Direction lightFace(int bits) {
|
||||
return ModelHelper.faceFromIndex((bits >> LIGHT_SHIFT) & DIRECTION_MASK);
|
||||
}
|
||||
|
||||
static int lightFace(int bits, Direction face) {
|
||||
return (bits & LIGHT_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << LIGHT_SHIFT);
|
||||
}
|
||||
|
||||
/** indicate if vertex normal has been set - bits correspond to vertex ordinals */
|
||||
static int normalFlags(int bits) {
|
||||
return (bits >> NORMALS_SHIFT) & NORMALS_MASK;
|
||||
}
|
||||
|
||||
static int normalFlags(int bits, int normalFlags) {
|
||||
return (bits & NORMALS_INVERSE_MASK) | ((normalFlags & NORMALS_MASK) << NORMALS_SHIFT);
|
||||
}
|
||||
|
||||
static int geometryFlags(int bits) {
|
||||
return (bits >> GEOMETRY_SHIFT) & GEOMETRY_MASK;
|
||||
}
|
||||
|
||||
static int geometryFlags(int bits, int geometryFlags) {
|
||||
return (bits & GEOMETRY_INVERSE_MASK) | ((geometryFlags & GEOMETRY_MASK) << GEOMETRY_SHIFT);
|
||||
}
|
||||
|
||||
static RenderMaterialImpl.Value material(int bits) {
|
||||
return RenderMaterialImpl.byIndex((bits >> MATERIAL_SHIFT) & MATERIAL_MASK);
|
||||
}
|
||||
|
||||
static int material(int bits, RenderMaterialImpl.Value material) {
|
||||
return (bits & MATERIAL_INVERSE_MASK) | (material.index() << MATERIAL_SHIFT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,55 +30,58 @@ import net.fabricmc.indigo.renderer.helper.GeometryHelper;
|
|||
* The one interesting bit is in {@link Maker#emit()}.
|
||||
*/
|
||||
public class MeshBuilderImpl implements MeshBuilder {
|
||||
int[] data = new int[256];
|
||||
private final Maker maker = new Maker();
|
||||
int index = 0;
|
||||
int limit = data.length;
|
||||
|
||||
protected void ensureCapacity(int stride) {
|
||||
if(stride > limit - index) {
|
||||
limit *= 2;
|
||||
int[] bigger = new int[limit];
|
||||
System.arraycopy(data, 0, bigger, 0, index);
|
||||
data = bigger;
|
||||
maker.data = bigger;
|
||||
}
|
||||
}
|
||||
int[] data = new int[256];
|
||||
private final Maker maker = new Maker();
|
||||
int index = 0;
|
||||
int limit = data.length;
|
||||
|
||||
@Override
|
||||
public Mesh build() {
|
||||
int[] packed = new int[index];
|
||||
System.arraycopy(data, 0, packed, 0, index);
|
||||
index = 0;
|
||||
maker.begin(data, index);
|
||||
return new MeshImpl(packed);
|
||||
}
|
||||
protected void ensureCapacity(int stride) {
|
||||
if (stride > limit - index) {
|
||||
limit *= 2;
|
||||
int[] bigger = new int[limit];
|
||||
System.arraycopy(data, 0, bigger, 0, index);
|
||||
data = bigger;
|
||||
maker.data = bigger;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
ensureCapacity(EncodingFormat.MAX_STRIDE);
|
||||
maker.begin(data, index);
|
||||
return maker;
|
||||
}
|
||||
@Override
|
||||
public Mesh build() {
|
||||
int[] packed = new int[index];
|
||||
System.arraycopy(data, 0, packed, 0, index);
|
||||
index = 0;
|
||||
maker.begin(data, index);
|
||||
return new MeshImpl(packed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Our base classes are used differently so we define final
|
||||
* encoding steps in subtypes. This will be a static mesh used
|
||||
* at render time so we want to capture all geometry now and
|
||||
* apply non-location-dependent lighting.
|
||||
*/
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace = GeometryHelper.lightFace(this);
|
||||
geometryFlags = GeometryHelper.computeShapeFlags(this);
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
encodeHeader();
|
||||
index += maker.stride();
|
||||
ensureCapacity(EncodingFormat.MAX_STRIDE);
|
||||
baseIndex = index;
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
ensureCapacity(EncodingFormat.TOTAL_STRIDE);
|
||||
maker.begin(data, index);
|
||||
return maker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our base classes are used differently so we define final
|
||||
* encoding steps in subtypes. This will be a static mesh used
|
||||
* at render time so we want to capture all geometry now and
|
||||
* apply non-location-dependent lighting.
|
||||
*/
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace(GeometryHelper.lightFace(this));
|
||||
|
||||
if (isGeometryInvalid) {
|
||||
geometryFlags(GeometryHelper.computeShapeFlags(this));
|
||||
}
|
||||
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
index += EncodingFormat.TOTAL_STRIDE;
|
||||
ensureCapacity(EncodingFormat.TOTAL_STRIDE);
|
||||
baseIndex = index;
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,42 +21,42 @@ import java.util.function.Consumer;
|
|||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link Mesh}.
|
||||
* The way we encode meshes makes it very simple.
|
||||
*/
|
||||
public class MeshImpl implements Mesh {
|
||||
/** Used to satisfy external calls to {@link #forEach(Consumer)}. */
|
||||
ThreadLocal<QuadViewImpl> POOL = ThreadLocal.withInitial(QuadViewImpl::new);
|
||||
|
||||
final int[] data;
|
||||
/** Used to satisfy external calls to {@link #forEach(Consumer)}. */
|
||||
ThreadLocal<QuadViewImpl> POOL = ThreadLocal.withInitial(QuadViewImpl::new);
|
||||
|
||||
MeshImpl(int data[]) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int[] data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<QuadView> consumer) {
|
||||
forEach(consumer, POOL.get());
|
||||
}
|
||||
final int[] data;
|
||||
|
||||
/**
|
||||
* The renderer will call this with it's own cursor
|
||||
* to avoid the performance hit of a thread-local lookup.
|
||||
* Also means renderer can hold final references to quad buffers.
|
||||
*/
|
||||
void forEach(Consumer<QuadView> consumer, QuadViewImpl cursor) {
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
while(index < limit) {
|
||||
cursor.load(data, index);
|
||||
consumer.accept(cursor);
|
||||
index += cursor.stride();
|
||||
}
|
||||
}
|
||||
MeshImpl(int data[]) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int[] data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<QuadView> consumer) {
|
||||
forEach(consumer, POOL.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* The renderer will call this with it's own cursor
|
||||
* to avoid the performance hit of a thread-local lookup.
|
||||
* Also means renderer can hold final references to quad buffers.
|
||||
*/
|
||||
void forEach(Consumer<QuadView> consumer, QuadViewImpl cursor) {
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
|
||||
while (index < limit) {
|
||||
cursor.load(data, index);
|
||||
consumer.accept(cursor);
|
||||
index += EncodingFormat.TOTAL_STRIDE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,19 @@
|
|||
package net.fabricmc.indigo.renderer.mesh;
|
||||
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.EMPTY;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_START_OFFSET;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_BITS;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_TAG;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_LIGHTMAP;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_NORMAL;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_U;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_X;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||
|
@ -35,136 +47,151 @@ import net.minecraft.util.math.Direction;
|
|||
* because that depends on where/how it is used. (Mesh encoding vs. render-time transformation).
|
||||
*/
|
||||
public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEmitter, ShadeableQuad {
|
||||
public final void begin(int[] data, int baseIndex) {
|
||||
this.data = data;
|
||||
this.baseIndex = baseIndex;
|
||||
clear();
|
||||
}
|
||||
public final void begin(int[] data, int baseIndex) {
|
||||
this.data = data;
|
||||
this.baseIndex = baseIndex;
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
System.arraycopy(EMPTY, 0, data, baseIndex, EncodingFormat.MAX_STRIDE);
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = true;
|
||||
normalFlags = 0;
|
||||
tag = 0;
|
||||
colorIndex = -1;
|
||||
cullFace = null;
|
||||
lightFace = null;
|
||||
nominalFace = null;
|
||||
material = IndigoRenderer.MATERIAL_STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl material(RenderMaterial material) {
|
||||
if(material == null || material.spriteDepth() > this.material.spriteDepth()) {
|
||||
throw new UnsupportedOperationException("Material texture depth must be the same or less than original material.");
|
||||
}
|
||||
this.material = (Value)material;
|
||||
return this;
|
||||
}
|
||||
public void clear() {
|
||||
System.arraycopy(EMPTY, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = true;
|
||||
nominalFace = null;
|
||||
normalFlags(0);
|
||||
tag(0);
|
||||
colorIndex(-1);
|
||||
cullFace(null);
|
||||
material(IndigoRenderer.MATERIAL_STANDARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl cullFace(Direction face) {
|
||||
cullFace = face;
|
||||
nominalFace = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final MutableQuadViewImpl lightFace(Direction face) {
|
||||
lightFace = face;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final MutableQuadViewImpl material(RenderMaterial material) {
|
||||
if (material == null) {
|
||||
material = IndigoRenderer.MATERIAL_STANDARD;
|
||||
}
|
||||
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.material(data[baseIndex + HEADER_BITS], (Value) material);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl nominalFace(Direction face) {
|
||||
nominalFace = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl colorIndex(int colorIndex) {
|
||||
this.colorIndex = colorIndex;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final MutableQuadViewImpl cullFace(Direction face) {
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(data[baseIndex + HEADER_BITS], face);
|
||||
nominalFace(face);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl tag(int tag) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex, boolean isItem) {
|
||||
final int vertexStart = vertexStart();
|
||||
System.arraycopy(quadData, startIndex, data, vertexStart, 32);
|
||||
this.invalidateShape();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFaceAligned() {
|
||||
return (geometryFlags() & GeometryHelper.AXIS_ALIGNED_FLAG) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsDiffuseShading(int textureIndex) {
|
||||
return textureIndex < material.spriteDepth() && !material.disableDiffuse(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) {
|
||||
final int index = vertexStart() + vertexIndex * 8;
|
||||
data[index] = Float.floatToRawIntBits(x);
|
||||
data[index + 1] = Float.floatToRawIntBits(y);
|
||||
data[index + 2] = Float.floatToRawIntBits(z);
|
||||
isFaceNormalInvalid = true;
|
||||
return this;
|
||||
}
|
||||
public final MutableQuadViewImpl lightFace(Direction face) {
|
||||
Preconditions.checkNotNull(face);
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
|
||||
normalFlags |= (1 << vertexIndex);
|
||||
data[baseIndex + vertexIndex * 8 + 7 + VERTEX_START_OFFSET] = NormalHelper.packNormal(x, y, z, 0);
|
||||
return this;
|
||||
}
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], face);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper method. Copies face normals to vertex normals lacking one.
|
||||
*/
|
||||
public final void populateMissingNormals() {
|
||||
final int normalFlags = this.normalFlags;
|
||||
if (normalFlags == 0b1111) return;
|
||||
final int packedFaceNormal = NormalHelper.packNormal(faceNormal(), 0);
|
||||
for (int v = 0; v < 4; v++) {
|
||||
if ((normalFlags & (1 << v)) == 0) {
|
||||
data[baseIndex + v * 8 + 7 + VERTEX_START_OFFSET] = packedFaceNormal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) {
|
||||
data[baseIndex + vertexIndex * 8 + 6 + VERTEX_START_OFFSET] = lightmap;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final MutableQuadViewImpl nominalFace(Direction face) {
|
||||
nominalFace = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl spriteColor(int vertexIndex, int textureIndex, int color) {
|
||||
data[baseIndex + colorIndex(vertexIndex, textureIndex)] = color;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final MutableQuadViewImpl colorIndex(int colorIndex) {
|
||||
data[baseIndex + HEADER_COLOR_INDEX] = colorIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl sprite(int vertexIndex, int textureIndex, float u, float v) {
|
||||
final int i = baseIndex + colorIndex(vertexIndex, textureIndex) + 1;
|
||||
data[i] = Float.floatToRawIntBits(u);
|
||||
data[i + 1] = Float.floatToRawIntBits(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl spriteBake(int spriteIndex, Sprite sprite, int bakeFlags) {
|
||||
TextureHelper.bakeSprite(this, spriteIndex, sprite, bakeFlags);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final MutableQuadViewImpl tag(int tag) {
|
||||
data[baseIndex + HEADER_TAG] = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex, boolean isItem) {
|
||||
System.arraycopy(quadData, startIndex, data, baseIndex + HEADER_STRIDE, QUAD_STRIDE);
|
||||
this.invalidateShape();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFaceAligned() {
|
||||
return (geometryFlags() & GeometryHelper.AXIS_ALIGNED_FLAG) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsDiffuseShading(int textureIndex) {
|
||||
return textureIndex == 0 && !material().disableDiffuse(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) {
|
||||
final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X;
|
||||
data[index] = Float.floatToRawIntBits(x);
|
||||
data[index + 1] = Float.floatToRawIntBits(y);
|
||||
data[index + 2] = Float.floatToRawIntBits(z);
|
||||
isFaceNormalInvalid = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void normalFlags(int flags) {
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS], flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
|
||||
normalFlags(normalFlags() | (1 << vertexIndex));
|
||||
data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormalHelper.packNormal(x, y, z, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper method. Copies face normals to vertex normals lacking one.
|
||||
* Does not set flags that indicate a vertex normal was input.
|
||||
*/
|
||||
public final void populateMissingNormals() {
|
||||
final int normalFlags = this.normalFlags();
|
||||
|
||||
if (normalFlags == 0b1111) return;
|
||||
|
||||
final int packedFaceNormal = NormalHelper.packNormal(faceNormal(), 0);
|
||||
|
||||
for (int v = 0; v < 4; v++) {
|
||||
if ((normalFlags & (1 << v)) == 0) {
|
||||
data[baseIndex + v * VERTEX_STRIDE + VERTEX_NORMAL] = packedFaceNormal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) {
|
||||
data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP] = lightmap;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl spriteColor(int vertexIndex, int spriteIndex, int color) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR] = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl sprite(int vertexIndex, int spriteIndex, float u, float v) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
final int i = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U;
|
||||
data[i] = Float.floatToRawIntBits(u);
|
||||
data[i + 1] = Float.floatToRawIntBits(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableQuadViewImpl spriteBake(int spriteIndex, Sprite sprite, int bakeFlags) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
TextureHelper.bakeSprite(this, spriteIndex, sprite, bakeFlags);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,20 @@ package net.fabricmc.indigo.renderer.mesh;
|
|||
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_BITS;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_MATERIAL;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.HEADER_TAG;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.SECOND_TEXTURE_OFFSET;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.TEXTURE_OFFSET_MINUS;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.TEXTURE_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.THIRD_TEXTURE_OFFSET;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_START_OFFSET;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_LIGHTMAP;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_NORMAL;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_STRIDE;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_U;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_V;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_X;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_Y;
|
||||
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_Z;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
|
||||
|
@ -39,299 +46,241 @@ import net.minecraft.util.math.Direction;
|
|||
* of maintaining and encoding the quad state.
|
||||
*/
|
||||
public class QuadViewImpl implements QuadView {
|
||||
protected RenderMaterialImpl.Value material;
|
||||
protected Direction cullFace;
|
||||
protected Direction nominalFace;
|
||||
protected Direction lightFace;
|
||||
protected int colorIndex = -1;
|
||||
protected int tag = 0;
|
||||
/** indicate if vertex normal has been set - bits correspond to vertex ordinals */
|
||||
protected int normalFlags;
|
||||
protected int geometryFlags;
|
||||
protected boolean isGeometryInvalid = true;
|
||||
protected final Vector3f faceNormal = new Vector3f();
|
||||
protected boolean isFaceNormalInvalid = true;
|
||||
|
||||
/** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */
|
||||
protected int[] data;
|
||||
|
||||
/** Beginning of the quad. Also the header index. */
|
||||
protected int baseIndex = 0;
|
||||
|
||||
/**
|
||||
* Use when subtype is "attached" to a pre-existing array.
|
||||
* Sets data reference and index and decodes state from array.
|
||||
*/
|
||||
final void load(int[] data, int baseIndex) {
|
||||
this.data = data;
|
||||
this.baseIndex = baseIndex;
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used on vanilla quads or other quads that don't have encoded shape info
|
||||
* to signal that such should be computed when requested.
|
||||
*/
|
||||
public final void invalidateShape() {
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #load(int[], int)} but assumes array and index already set.
|
||||
* Only does the decoding part.
|
||||
*/
|
||||
public final void load() {
|
||||
// face normal isn't encoded but geometry flags are
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = false;
|
||||
decodeHeader();
|
||||
}
|
||||
|
||||
/** Reference to underlying array. Use with caution. Meant for fast renderer access */
|
||||
public int[] data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** True if any vertex normal has been set. */
|
||||
public boolean hasVertexNormals() {
|
||||
return normalFlags != 0;
|
||||
}
|
||||
|
||||
/** Index after header where vertex data starts (first 28 will be vanilla format. */
|
||||
public int vertexStart() {
|
||||
return baseIndex + VERTEX_START_OFFSET;
|
||||
}
|
||||
|
||||
/** Length of encoded quad in array, including header. */
|
||||
final int stride() {
|
||||
return EncodingFormat.stride(material.spriteDepth());
|
||||
}
|
||||
|
||||
/** reads state from header - vertex attributes are saved directly */
|
||||
protected void decodeHeader() {
|
||||
material = RenderMaterialImpl.byIndex(data[baseIndex + HEADER_MATERIAL]);
|
||||
final int bits = data[baseIndex + HEADER_BITS];
|
||||
colorIndex = data[baseIndex + HEADER_COLOR_INDEX];
|
||||
tag = data[baseIndex + HEADER_TAG];
|
||||
geometryFlags = EncodingFormat.geometryFlags(bits);
|
||||
cullFace = EncodingFormat.cullFace(bits);
|
||||
lightFace = EncodingFormat.lightFace(bits);
|
||||
nominalFace = lightFace;
|
||||
normalFlags = EncodingFormat.normalFlags(bits);
|
||||
}
|
||||
|
||||
/** writes state to header - vertex attributes are saved directly */
|
||||
protected void encodeHeader() {
|
||||
data[baseIndex + HEADER_MATERIAL] = material.index();
|
||||
data[baseIndex + HEADER_COLOR_INDEX] = colorIndex;
|
||||
data[baseIndex + HEADER_TAG] = tag;
|
||||
int bits = EncodingFormat.geometryFlags(0, geometryFlags);
|
||||
bits = EncodingFormat.normalFlags(bits, normalFlags);
|
||||
bits = EncodingFormat.cullFace(bits, cullFace);
|
||||
bits = EncodingFormat.lightFace(bits, lightFace);
|
||||
data[baseIndex + HEADER_BITS] = bits;
|
||||
}
|
||||
|
||||
/** gets flags used for lighting - lazily computed via {@link GeometryHelper#computeShapeFlags(QuadView)} */
|
||||
public int geometryFlags() {
|
||||
if(isGeometryInvalid) {
|
||||
isGeometryInvalid = false;
|
||||
geometryFlags = GeometryHelper.computeShapeFlags(this);
|
||||
}
|
||||
return geometryFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to override geometric analysis for compatibility edge case
|
||||
*/
|
||||
public void geometryFlags(int flags) {
|
||||
isGeometryInvalid = false;
|
||||
geometryFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void toVanilla(int textureIndex, int[] target, int targetIndex, boolean isItem) {
|
||||
System.arraycopy(data, vertexStart(), target, targetIndex, 32);
|
||||
}
|
||||
protected Direction nominalFace;
|
||||
protected boolean isGeometryInvalid = true;
|
||||
protected final Vector3f faceNormal = new Vector3f();
|
||||
protected boolean isFaceNormalInvalid = true;
|
||||
|
||||
/**
|
||||
* Internal helper method. Copies color and UV for the given texture to target, assuming vanilla format.
|
||||
*/
|
||||
public final void copyColorUV(int textureIndex, int[] target, int targetIndex) {
|
||||
int indexTo = targetIndex + 3;
|
||||
int indexFrom;
|
||||
int strideFrom;
|
||||
if(textureIndex == 0) {
|
||||
indexFrom = baseIndex + VERTEX_START_OFFSET + 3;
|
||||
strideFrom = 8;
|
||||
} else {
|
||||
indexFrom = baseIndex + (textureIndex == 1 ? SECOND_TEXTURE_OFFSET : THIRD_TEXTURE_OFFSET);
|
||||
strideFrom = 3;
|
||||
}
|
||||
for(int i = 0; i < 4; i++) {
|
||||
System.arraycopy(data, indexFrom, target, indexTo, 3);
|
||||
indexTo += 8;
|
||||
indexFrom += strideFrom;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RenderMaterialImpl.Value material() {
|
||||
return material;
|
||||
}
|
||||
/** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */
|
||||
protected int[] data;
|
||||
|
||||
@Override
|
||||
public final int colorIndex() {
|
||||
return colorIndex;
|
||||
}
|
||||
/** Beginning of the quad. Also the header index. */
|
||||
protected int baseIndex = 0;
|
||||
|
||||
@Override
|
||||
public final int tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Direction lightFace() {
|
||||
return lightFace;
|
||||
}
|
||||
/**
|
||||
* Use when subtype is "attached" to a pre-existing array.
|
||||
* Sets data reference and index and decodes state from array.
|
||||
*/
|
||||
final void load(int[] data, int baseIndex) {
|
||||
this.data = data;
|
||||
this.baseIndex = baseIndex;
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Direction cullFace() {
|
||||
return cullFace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Direction nominalFace() {
|
||||
return nominalFace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Vector3f faceNormal() {
|
||||
if(isFaceNormalInvalid) {
|
||||
NormalHelper.computeFaceNormal(faceNormal, this);
|
||||
isFaceNormalInvalid = false;
|
||||
}
|
||||
return faceNormal;
|
||||
}
|
||||
/**
|
||||
* Used on vanilla quads or other quads that don't have encoded shape info
|
||||
* to signal that such should be computed when requested.
|
||||
*/
|
||||
public final void invalidateShape() {
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(MutableQuadView target) {
|
||||
MutableQuadViewImpl quad = (MutableQuadViewImpl)target;
|
||||
|
||||
int len = Math.min(this.stride(), quad.stride());
|
||||
|
||||
// copy everything except the header/material
|
||||
System.arraycopy(data, baseIndex + 1, quad.data, quad.baseIndex + 1, len - 1);
|
||||
quad.isFaceNormalInvalid = this.isFaceNormalInvalid;
|
||||
if(!this.isFaceNormalInvalid) {
|
||||
quad.faceNormal.set(this.faceNormal.getX(), this.faceNormal.getY(), this.faceNormal.getZ());
|
||||
}
|
||||
quad.lightFace = this.lightFace;
|
||||
quad.colorIndex = this.colorIndex;
|
||||
quad.tag = this.tag;
|
||||
quad.cullFace = this.cullFace;
|
||||
quad.nominalFace = this.nominalFace;
|
||||
quad.normalFlags = this.normalFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f copyPos(int vertexIndex, Vector3f target) {
|
||||
if(target == null) {
|
||||
target = new Vector3f();
|
||||
}
|
||||
final int index = vertexStart() + vertexIndex * 8;
|
||||
target.set(
|
||||
Float.intBitsToFloat(data[index]),
|
||||
Float.intBitsToFloat(data[index + 1]),
|
||||
Float.intBitsToFloat(data[index + 2]));
|
||||
return target;
|
||||
}
|
||||
/**
|
||||
* Like {@link #load(int[], int)} but assumes array and index already set.
|
||||
* Only does the decoding part.
|
||||
*/
|
||||
public final void load() {
|
||||
// face normal isn't encoded but geometry flags are
|
||||
isFaceNormalInvalid = true;
|
||||
isGeometryInvalid = false;
|
||||
nominalFace = lightFace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float posByIndex(int vertexIndex, int coordinateIndex) {
|
||||
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 8 + coordinateIndex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float x(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 8]);
|
||||
}
|
||||
/** Reference to underlying array. Use with caution. Meant for fast renderer access */
|
||||
public int[] data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float y(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 8 + 1]);
|
||||
}
|
||||
public int normalFlags() {
|
||||
return EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float z(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 8 + 2]);
|
||||
}
|
||||
/** True if any vertex normal has been set. */
|
||||
public boolean hasVertexNormals() {
|
||||
return normalFlags() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNormal(int vertexIndex) {
|
||||
return (normalFlags & (1 << vertexIndex)) != 0;
|
||||
}
|
||||
/** gets flags used for lighting - lazily computed via {@link GeometryHelper#computeShapeFlags(QuadView)} */
|
||||
public int geometryFlags() {
|
||||
if (isGeometryInvalid) {
|
||||
isGeometryInvalid = false;
|
||||
final int result = GeometryHelper.computeShapeFlags(this);
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], result);
|
||||
return result;
|
||||
} else {
|
||||
return EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f copyNormal(int vertexIndex, Vector3f target) {
|
||||
if(hasNormal(vertexIndex)) {
|
||||
if(target == null) {
|
||||
target = new Vector3f();
|
||||
}
|
||||
final int normal = data[vertexStart() + vertexIndex * 8 + 7];
|
||||
target.set(
|
||||
NormalHelper.getPackedNormalComponent(normal, 0),
|
||||
NormalHelper.getPackedNormalComponent(normal, 1),
|
||||
NormalHelper.getPackedNormalComponent(normal, 2));
|
||||
return target;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Used to override geometric analysis for compatibility edge case
|
||||
*/
|
||||
public void geometryFlags(int flags) {
|
||||
isGeometryInvalid = false;
|
||||
data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalX(int vertexIndex) {
|
||||
return hasNormal(vertexIndex)
|
||||
? NormalHelper.getPackedNormalComponent(data[vertexStart() + vertexIndex * 8 + 7], 0)
|
||||
: Float.NaN;
|
||||
}
|
||||
@Override
|
||||
public final void toVanilla(int textureIndex, int[] target, int targetIndex, boolean isItem) {
|
||||
System.arraycopy(data, baseIndex + VERTEX_X, target, targetIndex, QUAD_STRIDE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalY(int vertexIndex) {
|
||||
return hasNormal(vertexIndex)
|
||||
? NormalHelper.getPackedNormalComponent(data[vertexStart() + vertexIndex * 8 + 7], 1)
|
||||
: Float.NaN;
|
||||
}
|
||||
@Override
|
||||
public final RenderMaterialImpl.Value material() {
|
||||
return EncodingFormat.material(data[baseIndex + HEADER_BITS]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalZ(int vertexIndex) {
|
||||
return hasNormal(vertexIndex)
|
||||
? NormalHelper.getPackedNormalComponent(data[vertexStart() + vertexIndex * 8 + 7], 2)
|
||||
: Float.NaN;
|
||||
}
|
||||
@Override
|
||||
public final int colorIndex() {
|
||||
return data[baseIndex + HEADER_COLOR_INDEX];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lightmap(int vertexIndex) {
|
||||
return data[baseIndex + vertexIndex * 8 + 6 + VERTEX_START_OFFSET];
|
||||
}
|
||||
@Override
|
||||
public final int tag() {
|
||||
return data[baseIndex + HEADER_TAG];
|
||||
}
|
||||
|
||||
protected int colorIndex(int vertexIndex, int textureIndex) {
|
||||
return textureIndex == 0 ? vertexIndex * 8 + 3 + VERTEX_START_OFFSET : TEXTURE_OFFSET_MINUS + textureIndex * TEXTURE_STRIDE + vertexIndex * 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int spriteColor(int vertexIndex, int textureIndex) {
|
||||
return data[baseIndex + colorIndex(vertexIndex, textureIndex)];
|
||||
}
|
||||
@Override
|
||||
public final Direction lightFace() {
|
||||
return EncodingFormat.lightFace(data[baseIndex + HEADER_BITS]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float spriteU(int vertexIndex, int textureIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + colorIndex(vertexIndex, textureIndex) + 1]);
|
||||
}
|
||||
@Override
|
||||
public final Direction cullFace() {
|
||||
return EncodingFormat.cullFace(data[baseIndex + HEADER_BITS]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float spriteV(int vertexIndex, int textureIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + colorIndex(vertexIndex, textureIndex) + 2]);
|
||||
}
|
||||
@Override
|
||||
public final Direction nominalFace() {
|
||||
return nominalFace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Vector3f faceNormal() {
|
||||
if (isFaceNormalInvalid) {
|
||||
NormalHelper.computeFaceNormal(faceNormal, this);
|
||||
isFaceNormalInvalid = false;
|
||||
}
|
||||
|
||||
return faceNormal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(MutableQuadView target) {
|
||||
MutableQuadViewImpl quad = (MutableQuadViewImpl) target;
|
||||
// copy everything except the header/material
|
||||
System.arraycopy(data, baseIndex + 1, quad.data, quad.baseIndex + 1, EncodingFormat.TOTAL_STRIDE - 1);
|
||||
quad.isFaceNormalInvalid = this.isFaceNormalInvalid;
|
||||
|
||||
if (!this.isFaceNormalInvalid) {
|
||||
quad.faceNormal.set(faceNormal.getX(), faceNormal.getY(), faceNormal.getZ());
|
||||
}
|
||||
|
||||
quad.lightFace(lightFace());
|
||||
quad.colorIndex(colorIndex());
|
||||
quad.tag(tag());
|
||||
quad.cullFace(cullFace());
|
||||
quad.nominalFace = this.nominalFace;
|
||||
quad.normalFlags(normalFlags());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f copyPos(int vertexIndex, Vector3f target) {
|
||||
if (target == null) {
|
||||
target = new Vector3f();
|
||||
}
|
||||
|
||||
final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X;
|
||||
target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1]), Float.intBitsToFloat(data[index + 2]));
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float posByIndex(int vertexIndex, int coordinateIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X + coordinateIndex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float x(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float y(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Y]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float z(int vertexIndex) {
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Z]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNormal(int vertexIndex) {
|
||||
return (normalFlags() & (1 << vertexIndex)) != 0;
|
||||
}
|
||||
|
||||
protected final int normalIndex(int vertexIndex) {
|
||||
return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f copyNormal(int vertexIndex, Vector3f target) {
|
||||
if (hasNormal(vertexIndex)) {
|
||||
if (target == null) {
|
||||
target = new Vector3f();
|
||||
}
|
||||
final int normal = data[normalIndex(vertexIndex)];
|
||||
target.set(NormalHelper.getPackedNormalComponent(normal, 0), NormalHelper.getPackedNormalComponent(normal, 1), NormalHelper.getPackedNormalComponent(normal, 2));
|
||||
return target;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalX(int vertexIndex) {
|
||||
return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 0) : Float.NaN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalY(int vertexIndex) {
|
||||
return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 1) : Float.NaN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalZ(int vertexIndex) {
|
||||
return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 2) : Float.NaN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lightmap(int vertexIndex) {
|
||||
return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int spriteColor(int vertexIndex, int spriteIndex) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float spriteU(int vertexIndex, int spriteIndex) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float spriteV(int vertexIndex, int spriteIndex) {
|
||||
Preconditions.checkArgument(spriteIndex == 0, "Unsupported sprite index: %s", spriteIndex);
|
||||
|
||||
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]);
|
||||
}
|
||||
|
||||
public int vertexStart() {
|
||||
return baseIndex + HEADER_STRIDE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
public interface BufferBuilderOffsetAccessor {
|
||||
@Accessor
|
||||
double getOffsetX();
|
||||
|
||||
@Accessor
|
||||
double getOffsetY();
|
||||
|
||||
@Accessor
|
||||
double getOffsetZ();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.mixin;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessAmbientOcclusionCalculator;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
@Mixin(targets = "net.minecraft.client.render.block.BlockModelRenderer$AmbientOcclusionCalculator")
|
||||
public abstract class MixinAmbientOcclusionCalculator implements AccessAmbientOcclusionCalculator {
|
||||
@Shadow private float[] colorMultiplier;
|
||||
@Shadow private int[] brightness;
|
||||
|
||||
@Shadow
|
||||
public abstract void apply(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, Direction face, float[] aoData, BitSet controlBits);
|
||||
|
||||
@Override
|
||||
public float[] fabric_colorMultiplier() {
|
||||
return colorMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] fabric_brightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_apply(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, Direction face, float[] aoData, BitSet controlBits) {
|
||||
apply(blockRenderView, blockState, pos, face, aoData, controlBits);
|
||||
}
|
||||
}
|
|
@ -16,15 +16,19 @@
|
|||
|
||||
package net.fabricmc.indigo.renderer.mixin;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Random;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBlockModelRenderer;
|
||||
import net.fabricmc.indigo.renderer.aocalc.VanillaAoHelper;
|
||||
import net.fabricmc.indigo.renderer.render.BlockRenderContext;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
|
@ -32,20 +36,37 @@ import net.minecraft.client.render.BufferBuilder;
|
|||
import net.minecraft.client.render.block.BlockModelRenderer;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
|
||||
@Mixin(BlockModelRenderer.class)
|
||||
public abstract class MixinBlockModelRenderer {
|
||||
@Shadow protected BlockColors colorMap;
|
||||
private final ThreadLocal<BlockRenderContext> CONTEXTS = ThreadLocal.withInitial(BlockRenderContext::new);
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "tesselate", cancellable = true)
|
||||
private void hookTesselate(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, BufferBuilder buffer, boolean checkSides, Random rand, long seed, CallbackInfoReturnable<Boolean> ci) {
|
||||
if(!((FabricBakedModel)model).isVanillaAdapter()) {
|
||||
BlockRenderContext context = CONTEXTS.get();
|
||||
if(!context.isCallingVanilla()) {
|
||||
ci.setReturnValue(CONTEXTS.get().tesselate((BlockModelRenderer)(Object)this, blockView, model, state, pos, buffer, seed));
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract class MixinBlockModelRenderer implements AccessBlockModelRenderer {
|
||||
@Shadow
|
||||
protected BlockColors colorMap;
|
||||
|
||||
@Shadow
|
||||
protected abstract void updateShape(BlockRenderView blockView, BlockState blockState, BlockPos blockPos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits);
|
||||
|
||||
private final ThreadLocal<BlockRenderContext> CONTEXTS = ThreadLocal.withInitial(BlockRenderContext::new);
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "tesselate", cancellable = true)
|
||||
private void hookTesselate(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, BufferBuilder buffer, boolean checkSides, Random rand, long seed, CallbackInfoReturnable<Boolean> ci) {
|
||||
if (!((FabricBakedModel) model).isVanillaAdapter()) {
|
||||
BlockRenderContext context = CONTEXTS.get();
|
||||
|
||||
if (!context.isCallingVanilla()) {
|
||||
ci.setReturnValue(CONTEXTS.get().tesselate((BlockModelRenderer) (Object) this, blockView, model, state, pos, buffer, seed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "<init>*")
|
||||
private void onInit(CallbackInfo ci) {
|
||||
VanillaAoHelper.initialize((BlockModelRenderer)(Object) this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_updateShape(BlockRenderView blockView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
|
||||
updateShape(blockView, blockState, pos, vertexData, face, aoData, controlBits);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.spongepowered.asm.mixin.Shadow;
|
|||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
||||
import net.minecraft.client.render.BufferBuilder;
|
||||
import net.minecraft.client.render.VertexFormat;
|
||||
|
@ -30,78 +31,85 @@ import net.minecraft.client.render.VertexFormatElement;
|
|||
|
||||
@Mixin(BufferBuilder.class)
|
||||
public abstract class MixinBufferBuilder implements AccessBufferBuilder {
|
||||
@Shadow private IntBuffer bufInt;
|
||||
@Shadow private int vertexCount;
|
||||
@Shadow abstract void grow(int size);
|
||||
@Shadow abstract int getCurrentSize();
|
||||
@Shadow public abstract VertexFormat getVertexFormat();
|
||||
@Shadow
|
||||
private IntBuffer bufInt;
|
||||
@Shadow
|
||||
private int vertexCount;
|
||||
|
||||
private static final int VERTEX_STRIDE_INTS = 8;
|
||||
private static final int QUAD_STRIDE_INTS = VERTEX_STRIDE_INTS * 4;
|
||||
private static final int QUAD_STRIDE_BYTES = QUAD_STRIDE_INTS * 4;
|
||||
@Shadow
|
||||
abstract void grow(int size);
|
||||
|
||||
@Override
|
||||
public void fabric_putQuad(QuadViewImpl quad) {
|
||||
if (Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
bufferCompatibly(quad);
|
||||
} else {
|
||||
bufferFast(quad);
|
||||
}
|
||||
}
|
||||
@Shadow
|
||||
abstract int getCurrentSize();
|
||||
|
||||
private void bufferFast(QuadViewImpl quad) {
|
||||
grow(QUAD_STRIDE_BYTES + getVertexFormat().getVertexSize());
|
||||
bufInt.limit(bufInt.capacity());
|
||||
bufInt.position(getCurrentSize());
|
||||
bufInt.put(quad.data(), quad.vertexStart(), QUAD_STRIDE_INTS);
|
||||
vertexCount += 4;
|
||||
}
|
||||
@Shadow
|
||||
public abstract VertexFormat getVertexFormat();
|
||||
|
||||
/**
|
||||
* Uses buffer vertex format to drive buffer population.
|
||||
* Relies on logic elsewhere to ensure coordinates don't include chunk offset
|
||||
* (because buffer builder will handle that.)<p>
|
||||
*
|
||||
* Calling putVertexData() would likely be a little faster but this approach
|
||||
* gives us a chance to pass vertex normals to shaders, which isn't possible
|
||||
* with the standard block format. It also doesn't require us to encode a specific
|
||||
* custom format directly, which would be prone to breakage outside our control.
|
||||
*/
|
||||
private void bufferCompatibly(QuadViewImpl quad) {
|
||||
final VertexFormat format = getVertexFormat();;
|
||||
final int elementCount = format.getElementCount();
|
||||
for(int i = 0; i < 4; i++) {
|
||||
for(int j = 0; j < elementCount; j++) {
|
||||
VertexFormatElement e = format.getElement(j);
|
||||
switch(e.getType()) {
|
||||
case COLOR:
|
||||
final int c = quad.spriteColor(i, 0);
|
||||
((BufferBuilder)(Object)this).color(c & 0xFF, (c >>> 8) & 0xFF, (c >>> 16) & 0xFF, (c >>> 24) & 0xFF);
|
||||
break;
|
||||
case NORMAL:
|
||||
((BufferBuilder)(Object)this).normal(quad.normalX(i), quad.normalY(i), quad.normalZ(i));
|
||||
break;
|
||||
case POSITION:
|
||||
((BufferBuilder)(Object)this).vertex(quad.x(i), quad.y(i), quad.z(i));
|
||||
break;
|
||||
case UV:
|
||||
if(e.getIndex() == 0) {
|
||||
((BufferBuilder)(Object)this).texture(quad.spriteU(i, 0), quad.spriteV(i, 0));
|
||||
} else {
|
||||
final int b = quad.lightmap(i);
|
||||
((BufferBuilder)(Object)this).texture((b >> 16) & 0xFFFF, b & 0xFFFF);
|
||||
}
|
||||
break;
|
||||
@Override
|
||||
public void fabric_putQuad(QuadViewImpl quad) {
|
||||
if (Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
bufferCompatibly(quad);
|
||||
} else {
|
||||
bufferFast(quad);
|
||||
}
|
||||
}
|
||||
|
||||
// these types should never occur and/or require no action
|
||||
case PADDING:
|
||||
case GENERIC:
|
||||
default:
|
||||
break;
|
||||
private void bufferFast(QuadViewImpl quad) {
|
||||
grow(EncodingFormat.QUAD_STRIDE_BYTES + getVertexFormat().getVertexSize());
|
||||
bufInt.limit(bufInt.capacity());
|
||||
bufInt.position(getCurrentSize());
|
||||
bufInt.put(quad.data(), quad.vertexStart(), EncodingFormat.QUAD_STRIDE);
|
||||
vertexCount += 4;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
((BufferBuilder)(Object)this).next();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Uses buffer vertex format to drive buffer population.
|
||||
* Relies on logic elsewhere to ensure coordinates don't include chunk offset
|
||||
* (because buffer builder will handle that.)<p>
|
||||
*
|
||||
* Calling putVertexData() would likely be a little faster but this approach
|
||||
* gives us a chance to pass vertex normals to shaders, which isn't possible
|
||||
* with the standard block format. It also doesn't require us to encode a specific
|
||||
* custom format directly, which would be prone to breakage outside our control.
|
||||
*/
|
||||
private void bufferCompatibly(QuadViewImpl quad) {
|
||||
final VertexFormat format = getVertexFormat();
|
||||
final int elementCount = format.getElementCount();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < elementCount; j++) {
|
||||
VertexFormatElement e = format.getElement(j);
|
||||
|
||||
switch (e.getType()) {
|
||||
case COLOR:
|
||||
final int c = quad.spriteColor(i, 0);
|
||||
((BufferBuilder) (Object) this).color(c & 0xFF, (c >>> 8) & 0xFF, (c >>> 16) & 0xFF, (c >>> 24) & 0xFF);
|
||||
break;
|
||||
case NORMAL:
|
||||
((BufferBuilder) (Object) this).normal(quad.normalX(i), quad.normalY(i), quad.normalZ(i));
|
||||
break;
|
||||
case POSITION:
|
||||
((BufferBuilder) (Object) this).vertex(quad.x(i), quad.y(i), quad.z(i));
|
||||
break;
|
||||
case UV:
|
||||
if (e.getIndex() == 0) {
|
||||
((BufferBuilder) (Object) this).texture(quad.spriteU(i, 0), quad.spriteV(i, 0));
|
||||
} else {
|
||||
final int b = quad.lightmap(i);
|
||||
((BufferBuilder) (Object) this).texture((b >> 16) & 0xFFFF, b & 0xFFFF);
|
||||
}
|
||||
break;
|
||||
|
||||
// these types should never occur and/or require no action
|
||||
case PADDING:
|
||||
case GENERIC:
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
((BufferBuilder) (Object) this).next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,24 +61,20 @@ import net.minecraft.world.BlockRenderView;
|
|||
*/
|
||||
@Mixin(targets = "net.minecraft.client.render.chunk.ChunkBatcher$ChunkRenderer$class_4578")
|
||||
public class MixinChunkRebuildTask {
|
||||
@Shadow protected ChunkRendererRegion field_20838;
|
||||
@Shadow protected ChunkRenderer field_20839;
|
||||
|
||||
@Shadow
|
||||
protected ChunkRendererRegion field_20838;
|
||||
@Shadow
|
||||
protected ChunkRenderer field_20839;
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "method_22785")
|
||||
private void hookChunkBuild(
|
||||
float float_1,
|
||||
float float_2,
|
||||
float float_3,
|
||||
ChunkBatcher.ChunkRenderData renderData,
|
||||
BlockLayeredBufferBuilder builder,
|
||||
CallbackInfoReturnable<Set<BlockEntity>> ci) {
|
||||
private void hookChunkBuild(float float_1, float float_2, float float_3, ChunkBatcher.ChunkRenderData renderData, BlockLayeredBufferBuilder builder, CallbackInfoReturnable<Set<BlockEntity>> ci) {
|
||||
if (field_20838 != null) {
|
||||
TerrainRenderContext renderer = TerrainRenderContext.POOL.get();
|
||||
renderer.prepare(field_20838, field_20839, renderData, builder);
|
||||
((AccessChunkRendererRegion)field_20838).fabric_setRenderer(renderer);
|
||||
((AccessChunkRendererRegion) field_20838).fabric_setRenderer(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the hook that actually implements the rendering API for terrain rendering.<p>
|
||||
*
|
||||
|
@ -95,8 +91,7 @@ public class MixinChunkRebuildTask {
|
|||
* Normally this does nothing but will allow mods to create rendering hooks that are
|
||||
* driven off of render type. (Not recommended or encouraged, but also not prevented.)
|
||||
*/
|
||||
@Redirect(method = "method_22785", require = 1,
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;tesselateBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/render/BufferBuilder;Ljava/util/Random;)Z"))
|
||||
@Redirect(method = "method_22785", require = 1, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;tesselateBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/render/BufferBuilder;Ljava/util/Random;)Z"))
|
||||
private boolean hookChunkBuildTesselate(BlockRenderManager renderManager, BlockState blockState, BlockPos blockPos, BlockRenderView blockView, BufferBuilder bufferBuilder, Random random) {
|
||||
if (blockState.getRenderType() == BlockRenderType.MODEL) {
|
||||
final BakedModel model = renderManager.getModel(blockState);
|
||||
|
|
|
@ -27,9 +27,12 @@ import net.minecraft.client.render.chunk.ChunkBatcher.ChunkRenderData;
|
|||
|
||||
@Mixin(ChunkRenderData.class)
|
||||
public class MixinChunkRenderData implements AccessChunkRendererData {
|
||||
@Shadow private Set<BlockRenderLayer> initialized;
|
||||
@Shadow private Set<BlockRenderLayer> nonEmpty;
|
||||
@Shadow private boolean empty;
|
||||
@Shadow
|
||||
private Set<BlockRenderLayer> initialized;
|
||||
@Shadow
|
||||
private Set<BlockRenderLayer> nonEmpty;
|
||||
@Shadow
|
||||
private boolean empty;
|
||||
|
||||
@Override
|
||||
public boolean fabric_markInitialized(BlockRenderLayer renderLayer) {
|
||||
|
|
|
@ -25,14 +25,15 @@ import net.minecraft.client.render.chunk.ChunkBatcher.ChunkRenderer;
|
|||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
@Mixin(ChunkRenderer.class)
|
||||
public abstract class MixinChunkRenderer implements AccessChunkRenderer{
|
||||
@Shadow abstract void beginBufferBuilding(BufferBuilder builder, BlockPos pos);
|
||||
public abstract class MixinChunkRenderer implements AccessChunkRenderer {
|
||||
@Shadow
|
||||
abstract void beginBufferBuilding(BufferBuilder builder, BlockPos pos);
|
||||
|
||||
/**
|
||||
* Access method for renderer.
|
||||
*/
|
||||
@Override
|
||||
public void fabric_beginBufferBuilding(BufferBuilder builder, BlockPos pos) {
|
||||
beginBufferBuilding(builder, pos);
|
||||
}
|
||||
/**
|
||||
* Access method for renderer.
|
||||
*/
|
||||
@Override
|
||||
public void fabric_beginBufferBuilding(BufferBuilder builder, BlockPos pos) {
|
||||
beginBufferBuilding(builder, pos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,15 +24,15 @@ import net.minecraft.client.render.chunk.ChunkRendererRegion;
|
|||
|
||||
@Mixin(ChunkRendererRegion.class)
|
||||
public abstract class MixinChunkRendererRegion implements AccessChunkRendererRegion {
|
||||
private TerrainRenderContext fabric_renderer;
|
||||
|
||||
@Override
|
||||
public TerrainRenderContext fabric_getRenderer() {
|
||||
return fabric_renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_setRenderer(TerrainRenderContext renderer) {
|
||||
fabric_renderer = renderer;
|
||||
}
|
||||
private TerrainRenderContext fabric_renderer;
|
||||
|
||||
@Override
|
||||
public TerrainRenderContext fabric_getRenderer() {
|
||||
return fabric_renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_setRenderer(TerrainRenderContext renderer) {
|
||||
fabric_renderer = renderer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,27 +35,31 @@ import net.minecraft.item.ItemStack;
|
|||
|
||||
@Mixin(ItemRenderer.class)
|
||||
public abstract class MixinItemRenderer {
|
||||
@Shadow protected abstract void renderQuads(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack);
|
||||
@Shadow protected ItemColors colorMap;
|
||||
private final ThreadLocal<ItemRenderContext> CONTEXTS = ThreadLocal.withInitial(() -> new ItemRenderContext(colorMap));
|
||||
|
||||
/**
|
||||
* Save stack for enchantment glint renders - we won't otherwise have access to it
|
||||
* during the glint render because it receives an empty stack.
|
||||
*/
|
||||
@Inject(at = @At("HEAD"), method = "renderItemAndGlow")
|
||||
private void hookRenderItemAndGlow(ItemStack stack, BakedModel model, CallbackInfo ci) {
|
||||
if(stack.hasEnchantmentGlint() && !((FabricBakedModel)model).isVanillaAdapter()) {
|
||||
CONTEXTS.get().enchantmentStack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "renderModel", cancellable = true)
|
||||
private void hookRenderModel(BakedModel model, int color, ItemStack stack, CallbackInfo ci) {
|
||||
FabricBakedModel fabricModel = (FabricBakedModel)model;
|
||||
if(!fabricModel.isVanillaAdapter()) {
|
||||
CONTEXTS.get().renderModel(fabricModel, color, stack, this::renderQuads);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
@Shadow
|
||||
protected abstract void renderQuads(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack);
|
||||
|
||||
@Shadow
|
||||
protected ItemColors colorMap;
|
||||
private final ThreadLocal<ItemRenderContext> CONTEXTS = ThreadLocal.withInitial(() -> new ItemRenderContext(colorMap));
|
||||
|
||||
/**
|
||||
* Save stack for enchantment glint renders - we won't otherwise have access to it
|
||||
* during the glint render because it receives an empty stack.
|
||||
*/
|
||||
@Inject(at = @At("HEAD"), method = "renderItemAndGlow")
|
||||
private void hookRenderItemAndGlow(ItemStack stack, BakedModel model, CallbackInfo ci) {
|
||||
if (stack.hasEnchantmentGlint() && !((FabricBakedModel) model).isVanillaAdapter()) {
|
||||
CONTEXTS.get().enchantmentStack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "renderModel", cancellable = true)
|
||||
private void hookRenderModel(BakedModel model, int color, ItemStack stack, CallbackInfo ci) {
|
||||
final FabricBakedModel fabricModel = (FabricBakedModel) model;
|
||||
|
||||
if (!fabricModel.isVanillaAdapter()) {
|
||||
CONTEXTS.get().renderModel(fabricModel, color, stack, this::renderQuads);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
|||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
|
||||
import net.fabricmc.indigo.renderer.IndigoRenderer;
|
||||
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
|
||||
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.fabricmc.indigo.renderer.helper.ColorHelper;
|
||||
|
@ -40,117 +39,96 @@ import net.minecraft.client.MinecraftClient;
|
|||
* "editor" quad held in the instance, where all transformations are applied before buffering.
|
||||
*/
|
||||
public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implements Consumer<Mesh> {
|
||||
protected AbstractMeshConsumer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, bufferFunc, aoCalc, transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Where we handle all pre-buffer coloring, lighting, transformation, etc.
|
||||
* Reused for all mesh quads. Fixed baking array sized to hold largest possible mesh quad.
|
||||
*/
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
{
|
||||
data = new int[EncodingFormat.MAX_STRIDE];
|
||||
material = (Value) IndigoRenderer.INSTANCE.materialFinder().spriteDepth(RenderMaterialImpl.MAX_SPRITE_DEPTH).find();
|
||||
}
|
||||
|
||||
// only used via RenderContext.getEmitter()
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace = GeometryHelper.lightFace(this);
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad(this);
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
private final Maker editorQuad = new Maker();
|
||||
|
||||
@Override
|
||||
public void accept(Mesh mesh) {
|
||||
MeshImpl m = (MeshImpl)mesh;
|
||||
final int[] data = m.data();
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
while(index < limit) {
|
||||
RenderMaterialImpl.Value mat = RenderMaterialImpl.byIndex(data[index]);
|
||||
final int stride = EncodingFormat.stride(mat.spriteDepth());
|
||||
System.arraycopy(data, index, editorQuad.data(), 0, stride);
|
||||
editorQuad.load();
|
||||
index += stride;
|
||||
renderQuad(editorQuad);
|
||||
}
|
||||
}
|
||||
|
||||
public QuadEmitter getEmitter() {
|
||||
editorQuad.clear();
|
||||
return editorQuad;
|
||||
}
|
||||
|
||||
private void renderQuad(MutableQuadViewImpl q) {
|
||||
if(!transform.transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
protected AbstractMeshConsumer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, bufferFunc, aoCalc, transform);
|
||||
}
|
||||
|
||||
if(!blockInfo.shouldDrawFace(q.cullFace())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RenderMaterialImpl.Value mat = q.material();
|
||||
final int textureCount = mat.spriteDepth();
|
||||
/**
|
||||
* Where we handle all pre-buffer coloring, lighting, transformation, etc.
|
||||
* Reused for all mesh quads. Fixed baking array sized to hold largest possible mesh quad.
|
||||
*/
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
{
|
||||
data = new int[EncodingFormat.TOTAL_STRIDE];
|
||||
material(IndigoRenderer.MATERIAL_STANDARD);
|
||||
}
|
||||
|
||||
|
||||
if(mat.hasAo && MinecraftClient.isAmbientOcclusionEnabled()) {
|
||||
// needs to happen before offsets are applied
|
||||
aoCalc.compute(q, false);
|
||||
}
|
||||
|
||||
applyOffsets(q);
|
||||
|
||||
// if maybe mix of emissive / non-emissive layers then
|
||||
// need to save lightmaps in case they are overwritten by emissive
|
||||
if(mat.hasEmissive && textureCount > 1) {
|
||||
captureLightmaps(q);
|
||||
}
|
||||
|
||||
tesselateQuad(q, mat, 0);
|
||||
|
||||
for(int t = 1; t < textureCount; t++) {
|
||||
if(!mat.emissive(t)) {
|
||||
restoreLightmaps(q);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, q.spriteColor(i, t));
|
||||
q.sprite(i, 0, q.spriteU(i, t), q.spriteV(i, t));
|
||||
}
|
||||
tesselateQuad(q, mat, t);
|
||||
}
|
||||
}
|
||||
// only used via RenderContext.getEmitter()
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace(GeometryHelper.lightFace(this));
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad(this);
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
protected abstract void applyOffsets(MutableQuadViewImpl quad);
|
||||
|
||||
/**
|
||||
* Determines color index and render layer, then routes to appropriate
|
||||
* tesselate routine based on material properties.
|
||||
*/
|
||||
private void tesselateQuad(MutableQuadViewImpl quad, RenderMaterialImpl.Value mat, int textureIndex) {
|
||||
final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex();
|
||||
final BlockRenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex));
|
||||
|
||||
if(blockInfo.defaultAo && !mat.disableAo(textureIndex)) {
|
||||
if(mat.emissive(textureIndex)) {
|
||||
tesselateSmoothEmissive(quad, renderLayer, colorIndex);
|
||||
} else {
|
||||
tesselateSmooth(quad, renderLayer, colorIndex);
|
||||
}
|
||||
} else {
|
||||
if(mat.emissive(textureIndex)) {
|
||||
tesselateFlatEmissive(quad, renderLayer, colorIndex, lightmaps);
|
||||
} else {
|
||||
tesselateFlat(quad, renderLayer, colorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
private final Maker editorQuad = new Maker();
|
||||
|
||||
@Override
|
||||
public void accept(Mesh mesh) {
|
||||
MeshImpl m = (MeshImpl) mesh;
|
||||
final int[] data = m.data();
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
|
||||
while (index < limit) {
|
||||
System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE);
|
||||
editorQuad.load();
|
||||
index += EncodingFormat.TOTAL_STRIDE;
|
||||
renderQuad(editorQuad);
|
||||
}
|
||||
}
|
||||
|
||||
public QuadEmitter getEmitter() {
|
||||
editorQuad.clear();
|
||||
return editorQuad;
|
||||
}
|
||||
|
||||
private void renderQuad(MutableQuadViewImpl q) {
|
||||
if (!transform.transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!blockInfo.shouldDrawFace(q.cullFace())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RenderMaterialImpl.Value mat = q.material();
|
||||
|
||||
if (!mat.disableAo(0) && MinecraftClient.isAmbientOcclusionEnabled()) {
|
||||
// needs to happen before offsets are applied
|
||||
aoCalc.compute(q, false);
|
||||
}
|
||||
|
||||
applyOffsets(q);
|
||||
|
||||
tesselateQuad(q, mat, 0);
|
||||
}
|
||||
|
||||
protected abstract void applyOffsets(MutableQuadViewImpl quad);
|
||||
|
||||
/**
|
||||
* Determines color index and render layer, then routes to appropriate
|
||||
* tesselate routine based on material properties.
|
||||
*/
|
||||
private void tesselateQuad(MutableQuadViewImpl quad, RenderMaterialImpl.Value mat, int textureIndex) {
|
||||
final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex();
|
||||
final BlockRenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex));
|
||||
|
||||
if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) {
|
||||
if (mat.emissive(textureIndex)) {
|
||||
tesselateSmoothEmissive(quad, renderLayer, colorIndex);
|
||||
} else {
|
||||
tesselateSmooth(quad, renderLayer, colorIndex);
|
||||
}
|
||||
} else {
|
||||
if (mat.emissive(textureIndex)) {
|
||||
tesselateFlatEmissive(quad, renderLayer, colorIndex);
|
||||
} else {
|
||||
tesselateFlat(quad, renderLayer, colorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
|
|||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.fabricmc.indigo.renderer.helper.ColorHelper;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockRenderLayer;
|
||||
|
@ -36,113 +35,102 @@ import net.minecraft.util.math.BlockPos;
|
|||
* Has most of the actual buffer-time lighting and coloring logic.
|
||||
*/
|
||||
public abstract class AbstractQuadRenderer {
|
||||
private static final int FULL_BRIGHTNESS = 15 << 20 | 15 << 4;
|
||||
|
||||
protected final Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc;
|
||||
protected final BlockRenderInfo blockInfo;
|
||||
protected final AoCalculator aoCalc;
|
||||
protected final QuadTransform transform;
|
||||
|
||||
AbstractQuadRenderer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
this.blockInfo = blockInfo;
|
||||
this.bufferFunc = bufferFunc;
|
||||
this.aoCalc = aoCalc;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
|
||||
/** handles block color and red-blue swizzle, common to all renders */
|
||||
private void colorizeQuad(MutableQuadViewImpl q, int blockColorIndex) {
|
||||
if(blockColorIndex == -1) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(q.spriteColor(i, 0)));
|
||||
}
|
||||
} else {
|
||||
final int blockColor = blockInfo.blockColor(blockColorIndex);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(ColorHelper.multiplyColor(blockColor, q.spriteColor(i, 0))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** final output step, common to all renders */
|
||||
private void bufferQuad(MutableQuadViewImpl quad, BlockRenderLayer renderLayer) {
|
||||
bufferFunc.apply(renderLayer).fabric_putQuad(quad);
|
||||
}
|
||||
static final int FULL_BRIGHTNESS = 0xF000F0;
|
||||
|
||||
// routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop
|
||||
|
||||
/** for non-emissive mesh quads and all fallback quads with smooth lighting*/
|
||||
protected void tesselateSmooth(MutableQuadViewImpl q, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(q, blockColorIndex);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.multiplyRGB(q.spriteColor(i, 0), aoCalc.ao[i]));
|
||||
q.lightmap(i, ColorHelper.maxBrightness(q.lightmap(i), aoCalc.light[i]));
|
||||
}
|
||||
bufferQuad(q, renderLayer);
|
||||
}
|
||||
|
||||
/** for emissive mesh quads with smooth lighting*/
|
||||
protected void tesselateSmoothEmissive(MutableQuadViewImpl q, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(q, blockColorIndex);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.multiplyRGB(q.spriteColor(i, 0), aoCalc.ao[i]));
|
||||
q.lightmap(i, FULL_BRIGHTNESS);
|
||||
}
|
||||
bufferQuad(q, renderLayer);
|
||||
}
|
||||
|
||||
/** for non-emissive mesh quads and all fallback quads with flat lighting*/
|
||||
protected void tesselateFlat(MutableQuadViewImpl quad, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(quad, blockColorIndex);
|
||||
final int brightness = flatBrightness(quad, blockInfo.blockState, blockInfo.blockPos);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), brightness));
|
||||
}
|
||||
bufferQuad(quad, renderLayer);
|
||||
}
|
||||
|
||||
/** for emissive mesh quads with flat lighting*/
|
||||
protected void tesselateFlatEmissive(MutableQuadViewImpl quad, BlockRenderLayer renderLayer, int blockColorIndex, int[] lightmaps) {
|
||||
colorizeQuad(quad, blockColorIndex);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
quad.lightmap(i, FULL_BRIGHTNESS);
|
||||
}
|
||||
bufferQuad(quad, renderLayer);
|
||||
}
|
||||
|
||||
protected int[] lightmaps = new int[4];
|
||||
|
||||
protected void captureLightmaps(MutableQuadViewImpl q) {
|
||||
final int[] data = q.data();
|
||||
final int[] lightmaps = this.lightmaps;
|
||||
lightmaps[0] = data[EncodingFormat.VERTEX_START_OFFSET + 6];
|
||||
lightmaps[1] = data[EncodingFormat.VERTEX_START_OFFSET + 6 + 8];
|
||||
lightmaps[2] = data[EncodingFormat.VERTEX_START_OFFSET + 6 + 16];
|
||||
lightmaps[3] = data[EncodingFormat.VERTEX_START_OFFSET + 6 + 24];
|
||||
}
|
||||
|
||||
protected void restoreLightmaps(MutableQuadViewImpl q) {
|
||||
final int[] data = q.data();
|
||||
final int[] lightmaps = this.lightmaps;
|
||||
data[EncodingFormat.VERTEX_START_OFFSET + 6] = lightmaps[0];
|
||||
data[EncodingFormat.VERTEX_START_OFFSET + 6 + 8] = lightmaps[1];
|
||||
data[EncodingFormat.VERTEX_START_OFFSET + 6 + 16] = lightmaps[2];
|
||||
data[EncodingFormat.VERTEX_START_OFFSET + 6 + 24] = lightmaps[3];
|
||||
}
|
||||
|
||||
private final BlockPos.Mutable mpos = new BlockPos.Mutable();
|
||||
|
||||
/**
|
||||
* Handles geometry-based check for using self brightness or neighbor brightness.
|
||||
* That logic only applies in flat lighting.
|
||||
*/
|
||||
int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) {
|
||||
mpos.set(pos);
|
||||
if((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) {
|
||||
mpos.setOffset(quad.lightFace());
|
||||
}
|
||||
// Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329
|
||||
return blockInfo.blockView.getLightmapIndex(blockState, mpos);
|
||||
}
|
||||
protected final Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc;
|
||||
protected final BlockRenderInfo blockInfo;
|
||||
protected final AoCalculator aoCalc;
|
||||
protected final QuadTransform transform;
|
||||
|
||||
AbstractQuadRenderer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
this.blockInfo = blockInfo;
|
||||
this.bufferFunc = bufferFunc;
|
||||
this.aoCalc = aoCalc;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
/** handles block color and red-blue swizzle, common to all renders */
|
||||
private void colorizeQuad(MutableQuadViewImpl q, int blockColorIndex) {
|
||||
if (blockColorIndex == -1) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(q.spriteColor(i, 0)));
|
||||
}
|
||||
} else {
|
||||
final int blockColor = blockInfo.blockColor(blockColorIndex);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(ColorHelper.multiplyColor(blockColor, q.spriteColor(i, 0))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** final output step, common to all renders */
|
||||
private void bufferQuad(MutableQuadViewImpl quad, BlockRenderLayer renderLayer) {
|
||||
bufferFunc.apply(renderLayer).fabric_putQuad(quad);
|
||||
}
|
||||
|
||||
// routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop
|
||||
|
||||
/** for non-emissive mesh quads and all fallback quads with smooth lighting*/
|
||||
protected void tesselateSmooth(MutableQuadViewImpl q, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(q, blockColorIndex);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.multiplyRGB(q.spriteColor(i, 0), aoCalc.ao[i]));
|
||||
q.lightmap(i, ColorHelper.maxBrightness(q.lightmap(i), aoCalc.light[i]));
|
||||
}
|
||||
|
||||
bufferQuad(q, renderLayer);
|
||||
}
|
||||
|
||||
/** for emissive mesh quads with smooth lighting*/
|
||||
protected void tesselateSmoothEmissive(MutableQuadViewImpl q, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(q, blockColorIndex);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.spriteColor(i, 0, ColorHelper.multiplyRGB(q.spriteColor(i, 0), aoCalc.ao[i]));
|
||||
q.lightmap(i, FULL_BRIGHTNESS);
|
||||
}
|
||||
|
||||
bufferQuad(q, renderLayer);
|
||||
}
|
||||
|
||||
/** for non-emissive mesh quads and all fallback quads with flat lighting*/
|
||||
protected void tesselateFlat(MutableQuadViewImpl quad, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(quad, blockColorIndex);
|
||||
final int brightness = flatBrightness(quad, blockInfo.blockState, blockInfo.blockPos);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), brightness));
|
||||
}
|
||||
|
||||
bufferQuad(quad, renderLayer);
|
||||
}
|
||||
|
||||
/** for emissive mesh quads with flat lighting*/
|
||||
protected void tesselateFlatEmissive(MutableQuadViewImpl quad, BlockRenderLayer renderLayer, int blockColorIndex) {
|
||||
colorizeQuad(quad, blockColorIndex);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
quad.lightmap(i, FULL_BRIGHTNESS);
|
||||
}
|
||||
|
||||
bufferQuad(quad, renderLayer);
|
||||
}
|
||||
|
||||
private final BlockPos.Mutable mpos = new BlockPos.Mutable();
|
||||
|
||||
/**
|
||||
* Handles geometry-based check for using self brightness or neighbor brightness.
|
||||
* That logic only applies in flat lighting.
|
||||
*/
|
||||
int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) {
|
||||
mpos.set(pos);
|
||||
|
||||
if ((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) {
|
||||
mpos.setOffset(quad.lightFace());
|
||||
}
|
||||
|
||||
// Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329
|
||||
return blockInfo.blockView.getLightmapIndex(blockState, mpos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,49 +21,54 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
|||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
|
||||
abstract class AbstractRenderContext implements RenderContext {
|
||||
private final ObjectArrayList<QuadTransform> transformStack = new ObjectArrayList<>();
|
||||
private static final QuadTransform NO_TRANSFORM = (q) -> true;
|
||||
|
||||
private final QuadTransform stackTransform = (q) -> {
|
||||
int i= transformStack.size() - 1;
|
||||
while(i >= 0) {
|
||||
if(!transformStack.get(i--).transform(q)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private QuadTransform activeTransform = NO_TRANSFORM;
|
||||
|
||||
protected final boolean transform(MutableQuadView q) {
|
||||
return activeTransform.transform(q);
|
||||
}
|
||||
|
||||
protected boolean hasTransform() {
|
||||
return activeTransform != NO_TRANSFORM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushTransform(QuadTransform transform) {
|
||||
if(transform == null) {
|
||||
throw new NullPointerException("Renderer received null QuadTransform.");
|
||||
}
|
||||
transformStack.push(transform);
|
||||
if(transformStack.size() == 1) {
|
||||
activeTransform = transform;
|
||||
} else if(transformStack.size() == 2) {
|
||||
activeTransform = stackTransform;
|
||||
}
|
||||
}
|
||||
private final ObjectArrayList<QuadTransform> transformStack = new ObjectArrayList<>();
|
||||
private static final QuadTransform NO_TRANSFORM = (q) -> true;
|
||||
|
||||
@Override
|
||||
public void popTransform() {
|
||||
transformStack.pop();
|
||||
if(transformStack.size() == 0) {
|
||||
activeTransform = NO_TRANSFORM;
|
||||
} else if (transformStack.size() == 1) {
|
||||
activeTransform = transformStack.get(0);
|
||||
}
|
||||
}
|
||||
private final QuadTransform stackTransform = (q) -> {
|
||||
int i = transformStack.size() - 1;
|
||||
|
||||
while (i >= 0) {
|
||||
if (!transformStack.get(i--).transform(q)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
private QuadTransform activeTransform = NO_TRANSFORM;
|
||||
|
||||
protected final boolean transform(MutableQuadView q) {
|
||||
return activeTransform.transform(q);
|
||||
}
|
||||
|
||||
protected boolean hasTransform() {
|
||||
return activeTransform != NO_TRANSFORM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushTransform(QuadTransform transform) {
|
||||
if (transform == null) {
|
||||
throw new NullPointerException("Renderer received null QuadTransform.");
|
||||
}
|
||||
|
||||
transformStack.push(transform);
|
||||
|
||||
if (transformStack.size() == 1) {
|
||||
activeTransform = transform;
|
||||
} else if (transformStack.size() == 2) {
|
||||
activeTransform = stackTransform;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popTransform() {
|
||||
transformStack.pop();
|
||||
|
||||
if (transformStack.size() == 0) {
|
||||
activeTransform = NO_TRANSFORM;
|
||||
} else if (transformStack.size() == 1) {
|
||||
activeTransform = transformStack.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,101 +41,103 @@ import net.minecraft.world.BlockRenderView;
|
|||
* Context for non-terrain block rendering.
|
||||
*/
|
||||
public class BlockRenderContext extends AbstractRenderContext implements RenderContext {
|
||||
private final BlockRenderInfo blockInfo = new BlockRenderInfo();
|
||||
private final AoCalculator aoCalc = new AoCalculator(blockInfo, this::brightness, this::aoLevel);
|
||||
private final MeshConsumer meshConsumer = new MeshConsumer(blockInfo, this::outputBuffer, aoCalc, this::transform);
|
||||
private final Random random = new Random();
|
||||
private BlockModelRenderer vanillaRenderer;
|
||||
private AccessBufferBuilder fabricBuffer;
|
||||
private long seed;
|
||||
private boolean isCallingVanilla = false;
|
||||
private boolean didOutput = false;
|
||||
|
||||
private double offsetX;
|
||||
private double offsetY;
|
||||
private double offsetZ;
|
||||
|
||||
public boolean isCallingVanilla() {
|
||||
return isCallingVanilla;
|
||||
}
|
||||
|
||||
private int brightness(BlockPos pos) {
|
||||
if(blockInfo.blockView == null) {
|
||||
return 15 << 20 | 15 << 4;
|
||||
}
|
||||
return blockInfo.blockView.getLightmapIndex(blockInfo.blockView.getBlockState(pos), pos);
|
||||
}
|
||||
private final BlockRenderInfo blockInfo = new BlockRenderInfo();
|
||||
private final AoCalculator aoCalc = new AoCalculator(blockInfo, this::brightness, this::aoLevel);
|
||||
private final MeshConsumer meshConsumer = new MeshConsumer(blockInfo, this::outputBuffer, aoCalc, this::transform);
|
||||
private final Random random = new Random();
|
||||
private BlockModelRenderer vanillaRenderer;
|
||||
private AccessBufferBuilder fabricBuffer;
|
||||
private long seed;
|
||||
private boolean isCallingVanilla = false;
|
||||
private boolean didOutput = false;
|
||||
|
||||
private float aoLevel(BlockPos pos) {
|
||||
final BlockRenderView blockView = blockInfo.blockView;
|
||||
return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
}
|
||||
|
||||
private AccessBufferBuilder outputBuffer(BlockRenderLayer renderLayer) {
|
||||
didOutput = true;
|
||||
return fabricBuffer;
|
||||
}
|
||||
|
||||
public boolean tesselate(BlockModelRenderer vanillaRenderer, BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, BufferBuilder buffer, long seed) {
|
||||
this.vanillaRenderer = vanillaRenderer;
|
||||
this.fabricBuffer = (AccessBufferBuilder) buffer;
|
||||
this.seed = seed;
|
||||
this.didOutput = false;
|
||||
aoCalc.clear();
|
||||
blockInfo.setBlockView(blockView);
|
||||
blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion());
|
||||
setupOffsets();
|
||||
|
||||
((FabricBakedModel)model).emitBlockQuads(blockView, state, pos, blockInfo.randomSupplier, this);
|
||||
|
||||
this.vanillaRenderer = null;
|
||||
blockInfo.release();
|
||||
this.fabricBuffer = null;
|
||||
return didOutput;
|
||||
}
|
||||
private double offsetX;
|
||||
private double offsetY;
|
||||
private double offsetZ;
|
||||
|
||||
protected void acceptVanillaModel(BakedModel model) {
|
||||
isCallingVanilla = true;
|
||||
didOutput = didOutput && vanillaRenderer.tesselate(blockInfo.blockView, model, blockInfo.blockState, blockInfo.blockPos, (BufferBuilder) fabricBuffer, false, random, seed);
|
||||
isCallingVanilla = false;
|
||||
}
|
||||
|
||||
private void setupOffsets() {
|
||||
final BufferBuilderOffsetAccessor buffer = (BufferBuilderOffsetAccessor) fabricBuffer;
|
||||
final BlockPos pos = blockInfo.blockPos;
|
||||
offsetX = buffer.getOffsetX() + pos.getX();
|
||||
offsetY = buffer.getOffsetY() + pos.getY();
|
||||
offsetZ = buffer.getOffsetZ() + pos.getZ();
|
||||
}
|
||||
|
||||
private class MeshConsumer extends AbstractMeshConsumer {
|
||||
MeshConsumer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, bufferFunc, aoCalc, transform);
|
||||
}
|
||||
public boolean isCallingVanilla() {
|
||||
return isCallingVanilla;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyOffsets(MutableQuadViewImpl q) {
|
||||
final double x = offsetX;
|
||||
final double y = offsetY;
|
||||
final double z = offsetZ;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.pos(i, (float)(q.x(i) + x), (float)(q.y(i) + y), (float)(q.z(i) + z));
|
||||
}
|
||||
}
|
||||
}
|
||||
private int brightness(BlockPos pos) {
|
||||
if (blockInfo.blockView == null) {
|
||||
return 15 << 20 | 15 << 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
return blockInfo.blockView.getLightmapIndex(blockInfo.blockView.getBlockState(pos), pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return this::acceptVanillaModel;
|
||||
}
|
||||
private float aoLevel(BlockPos pos) {
|
||||
final BlockRenderView blockView = blockInfo.blockView;
|
||||
return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
return meshConsumer.getEmitter();
|
||||
}
|
||||
private AccessBufferBuilder outputBuffer(BlockRenderLayer renderLayer) {
|
||||
didOutput = true;
|
||||
return fabricBuffer;
|
||||
}
|
||||
|
||||
public boolean tesselate(BlockModelRenderer vanillaRenderer, BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, BufferBuilder buffer, long seed) {
|
||||
this.vanillaRenderer = vanillaRenderer;
|
||||
this.fabricBuffer = (AccessBufferBuilder) buffer;
|
||||
this.seed = seed;
|
||||
this.didOutput = false;
|
||||
aoCalc.clear();
|
||||
blockInfo.setBlockView(blockView);
|
||||
blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion());
|
||||
setupOffsets();
|
||||
|
||||
((FabricBakedModel) model).emitBlockQuads(blockView, state, pos, blockInfo.randomSupplier, this);
|
||||
|
||||
this.vanillaRenderer = null;
|
||||
blockInfo.release();
|
||||
this.fabricBuffer = null;
|
||||
return didOutput;
|
||||
}
|
||||
|
||||
protected void acceptVanillaModel(BakedModel model) {
|
||||
isCallingVanilla = true;
|
||||
didOutput = didOutput && vanillaRenderer.tesselate(blockInfo.blockView, model, blockInfo.blockState, blockInfo.blockPos, (BufferBuilder) fabricBuffer, false, random, seed);
|
||||
isCallingVanilla = false;
|
||||
}
|
||||
|
||||
private void setupOffsets() {
|
||||
final BufferBuilderOffsetAccessor buffer = (BufferBuilderOffsetAccessor) fabricBuffer;
|
||||
final BlockPos pos = blockInfo.blockPos;
|
||||
offsetX = buffer.getOffsetX() + pos.getX();
|
||||
offsetY = buffer.getOffsetY() + pos.getY();
|
||||
offsetZ = buffer.getOffsetZ() + pos.getZ();
|
||||
}
|
||||
|
||||
private class MeshConsumer extends AbstractMeshConsumer {
|
||||
MeshConsumer(BlockRenderInfo blockInfo, Function<BlockRenderLayer, AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, bufferFunc, aoCalc, transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyOffsets(MutableQuadViewImpl q) {
|
||||
final double x = offsetX;
|
||||
final double y = offsetY;
|
||||
final double z = offsetZ;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.pos(i, (float) (q.x(i) + x), (float) (q.y(i) + y), (float) (q.z(i) + z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return this::acceptVanillaModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
return meshConsumer.getEmitter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,54 +36,56 @@ import net.minecraft.world.BlockRenderView;
|
|||
* so they can be applied together with chunk offsets.
|
||||
*/
|
||||
public class BlockRenderInfo {
|
||||
private final BlockColors blockColorMap = MinecraftClient.getInstance().getBlockColorMap();
|
||||
public final Random random = new Random();
|
||||
public BlockRenderView blockView;
|
||||
public BlockPos blockPos;
|
||||
public BlockState blockState;
|
||||
public long seed;
|
||||
boolean defaultAo;
|
||||
BlockRenderLayer defaultLayer;
|
||||
|
||||
public final Supplier<Random> randomSupplier = () -> {
|
||||
final Random result = random;
|
||||
long seed = this.seed;
|
||||
if(seed == -1L) {
|
||||
seed = blockState.getRenderingSeed(blockPos);
|
||||
this.seed = seed;
|
||||
}
|
||||
result.setSeed(seed);
|
||||
return result;
|
||||
};
|
||||
|
||||
public void setBlockView(BlockRenderView blockView) {
|
||||
this.blockView = blockView;
|
||||
}
|
||||
|
||||
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
|
||||
this.blockPos = blockPos;
|
||||
this.blockState = blockState;
|
||||
// in the unlikely case seed actually matches this, we'll simply retrieve it more than one
|
||||
seed = -1L;
|
||||
defaultAo = modelAO && MinecraftClient.isAmbientOcclusionEnabled() && blockState.getLuminance() == 0;
|
||||
|
||||
defaultLayer = BlockRenderLayer.method_22715(blockState);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
blockPos = null;
|
||||
blockState = null;
|
||||
}
|
||||
|
||||
int blockColor(int colorIndex) {
|
||||
return 0xFF000000 | blockColorMap.getColorMultiplier(blockState, blockView, blockPos, colorIndex);
|
||||
}
|
||||
|
||||
boolean shouldDrawFace(Direction face) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockRenderLayer effectiveRenderLayer(BlendMode blendMode) {
|
||||
return blendMode == BlendMode.DEFAULT ? this.defaultLayer : blendMode.blockRenderLayer;
|
||||
}
|
||||
private final BlockColors blockColorMap = MinecraftClient.getInstance().getBlockColorMap();
|
||||
public final Random random = new Random();
|
||||
public BlockRenderView blockView;
|
||||
public BlockPos blockPos;
|
||||
public BlockState blockState;
|
||||
public long seed;
|
||||
boolean defaultAo;
|
||||
BlockRenderLayer defaultLayer;
|
||||
|
||||
public final Supplier<Random> randomSupplier = () -> {
|
||||
final Random result = random;
|
||||
long seed = this.seed;
|
||||
|
||||
if (seed == -1L) {
|
||||
seed = blockState.getRenderingSeed(blockPos);
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
result.setSeed(seed);
|
||||
return result;
|
||||
};
|
||||
|
||||
public void setBlockView(BlockRenderView blockView) {
|
||||
this.blockView = blockView;
|
||||
}
|
||||
|
||||
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
|
||||
this.blockPos = blockPos;
|
||||
this.blockState = blockState;
|
||||
// in the unlikely case seed actually matches this, we'll simply retrieve it more than one
|
||||
seed = -1L;
|
||||
defaultAo = modelAO && MinecraftClient.isAmbientOcclusionEnabled() && blockState.getLuminance() == 0;
|
||||
|
||||
defaultLayer = BlockRenderLayer.method_22715(blockState);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
blockPos = null;
|
||||
blockState = null;
|
||||
}
|
||||
|
||||
int blockColor(int colorIndex) {
|
||||
return 0xFF000000 | blockColorMap.getColorMultiplier(blockState, blockView, blockPos, colorIndex);
|
||||
}
|
||||
|
||||
boolean shouldDrawFace(Direction face) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockRenderLayer effectiveRenderLayer(BlendMode blendMode) {
|
||||
return blendMode == BlendMode.DEFAULT ? this.defaultLayer : blendMode.blockRenderLayer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,152 +45,152 @@ import net.minecraft.world.BlockRenderView;
|
|||
* be applied together with chunk offsets.
|
||||
*/
|
||||
public class ChunkRenderInfo {
|
||||
/**
|
||||
* Serves same function as brightness cache in Mojang's AO calculator,
|
||||
* with some differences as follows...<p>
|
||||
*
|
||||
* 1) Mojang uses Object2Int. This uses Long2Int for performance and to avoid
|
||||
* creating new immutable BlockPos references. But will break if someone
|
||||
* wants to expand Y limit or world borders. If we want to support that may
|
||||
* need to switch or make configurable.<p>
|
||||
*
|
||||
* 2) Mojang overrides the map methods to limit the cache to 50 values.
|
||||
* However, a render chunk only has 18^3 blocks in it, and the cache is cleared every chunk.
|
||||
* For performance and simplicity, we just let map grow to the size of the render chunk.
|
||||
*
|
||||
* 3) Mojang only uses the cache for Ao. Here it is used for all brightness
|
||||
* lookups, including flat lighting.
|
||||
*
|
||||
* 4) The Mojang cache is a separate threadlocal with a threadlocal boolean to
|
||||
* enable disable. Cache clearing happens with the disable. There's no use case for
|
||||
* us when the cache needs to be disabled (and no apparent case in Mojang's code either)
|
||||
* so we simply clear the cache at the start of each new chunk. It is also
|
||||
* not a threadlocal because it's held within a threadlocal BlockRenderer.
|
||||
*/
|
||||
private final Long2IntOpenHashMap brightnessCache;
|
||||
private final Long2FloatOpenHashMap aoLevelCache;
|
||||
|
||||
private final BlockRenderInfo blockInfo;
|
||||
private final BlockPos.Mutable chunkOrigin = new BlockPos.Mutable();
|
||||
AccessChunkRendererData chunkData;
|
||||
ChunkRenderer chunkRenderer;
|
||||
BlockLayeredBufferBuilder builders;
|
||||
BlockRenderView blockView;
|
||||
|
||||
private final Object2ObjectOpenHashMap<BlockRenderLayer, AccessBufferBuilder> buffers = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
private double chunkOffsetX;
|
||||
private double chunkOffsetY;
|
||||
private double chunkOffsetZ;
|
||||
|
||||
// chunk offset + block pos offset + model offsets for plants, etc.
|
||||
private float offsetX = 0;
|
||||
private float offsetY = 0;
|
||||
private float offsetZ = 0;
|
||||
|
||||
ChunkRenderInfo(BlockRenderInfo blockInfo) {
|
||||
this.blockInfo = blockInfo;
|
||||
brightnessCache = new Long2IntOpenHashMap();
|
||||
brightnessCache.defaultReturnValue(Integer.MAX_VALUE);
|
||||
aoLevelCache = new Long2FloatOpenHashMap();
|
||||
aoLevelCache.defaultReturnValue(Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
void prepare(
|
||||
ChunkRendererRegion blockView,
|
||||
ChunkRenderer chunkRenderer,
|
||||
ChunkRenderData chunkData,
|
||||
BlockLayeredBufferBuilder builders) {
|
||||
this.blockView = blockView;
|
||||
this.chunkOrigin.set(chunkRenderer.getOrigin());
|
||||
this.chunkData = (AccessChunkRendererData) chunkData;
|
||||
this.chunkRenderer = chunkRenderer;
|
||||
this.builders = builders;
|
||||
buffers.clear();
|
||||
chunkOffsetX = -chunkOrigin.getX();
|
||||
chunkOffsetY = -chunkOrigin.getY();
|
||||
chunkOffsetZ = -chunkOrigin.getZ();
|
||||
brightnessCache.clear();
|
||||
aoLevelCache.clear();
|
||||
}
|
||||
|
||||
void release() {
|
||||
chunkData = null;
|
||||
chunkRenderer = null;
|
||||
buffers.clear();
|
||||
}
|
||||
|
||||
void beginBlock() {
|
||||
final BlockState blockState = blockInfo.blockState;
|
||||
final BlockPos blockPos = blockInfo.blockPos;
|
||||
|
||||
// When we are using the BufferBuilder input methods, the builder will
|
||||
// add the chunk offset for us, so we should only apply the block offset.
|
||||
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
offsetX = (float) (blockPos.getX());
|
||||
offsetY = (float) (blockPos.getY());
|
||||
offsetZ = (float) (blockPos.getZ());
|
||||
} else {
|
||||
offsetX = (float) (chunkOffsetX + blockPos.getX());
|
||||
offsetY = (float) (chunkOffsetY + blockPos.getY());
|
||||
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
|
||||
}
|
||||
|
||||
if(blockState.getBlock().getOffsetType() != OffsetType.NONE) {
|
||||
Vec3d offset = blockState.getOffsetPos(blockInfo.blockView, blockPos);
|
||||
offsetX += (float)offset.x;
|
||||
offsetY += (float)offset.y;
|
||||
offsetZ += (float)offset.z;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Lazily retrieves output buffer for given layer, initializing as needed. */
|
||||
public AccessBufferBuilder getInitializedBuffer(BlockRenderLayer renderLayer) {
|
||||
AccessBufferBuilder result = buffers.get(renderLayer);
|
||||
if (result == null) {
|
||||
BufferBuilder builder = builders.get(renderLayer);
|
||||
result = (AccessBufferBuilder) builder;
|
||||
chunkData.fabric_markPopulated(renderLayer);
|
||||
buffers.put(renderLayer, result);
|
||||
if (chunkData.fabric_markInitialized(renderLayer)) {
|
||||
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, chunkOrigin);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies position offset for chunk and, if present, block random offset.
|
||||
*/
|
||||
void applyOffsets(MutableQuadViewImpl q) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
q.pos(i, q.x(i) + offsetX, q.y(i) + offsetY, q.z(i) + offsetZ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached values for {@link BlockState#getBlockBrightness(BlockRenderView, BlockPos)}.
|
||||
* See also the comments for {@link #brightnessCache}.
|
||||
*/
|
||||
int cachedBrightness(BlockPos pos) {
|
||||
long key = pos.asLong();
|
||||
int result = brightnessCache.get(key);
|
||||
if (result == Integer.MAX_VALUE) {
|
||||
result = blockView.getLightmapIndex(blockView.getBlockState(pos), pos);
|
||||
brightnessCache.put(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
float cachedAoLevel(BlockPos pos)
|
||||
{
|
||||
long key = pos.asLong();
|
||||
float result = aoLevelCache.get(key);
|
||||
if (result == Float.MAX_VALUE) {
|
||||
result = AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
aoLevelCache.put(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Serves same function as brightness cache in Mojang's AO calculator,
|
||||
* with some differences as follows...<p>
|
||||
*
|
||||
* 1) Mojang uses Object2Int. This uses Long2Int for performance and to avoid
|
||||
* creating new immutable BlockPos references. But will break if someone
|
||||
* wants to expand Y limit or world borders. If we want to support that may
|
||||
* need to switch or make configurable.<p>
|
||||
*
|
||||
* 2) Mojang overrides the map methods to limit the cache to 50 values.
|
||||
* However, a render chunk only has 18^3 blocks in it, and the cache is cleared every chunk.
|
||||
* For performance and simplicity, we just let map grow to the size of the render chunk.
|
||||
*
|
||||
* 3) Mojang only uses the cache for Ao. Here it is used for all brightness
|
||||
* lookups, including flat lighting.
|
||||
*
|
||||
* 4) The Mojang cache is a separate threadlocal with a threadlocal boolean to
|
||||
* enable disable. Cache clearing happens with the disable. There's no use case for
|
||||
* us when the cache needs to be disabled (and no apparent case in Mojang's code either)
|
||||
* so we simply clear the cache at the start of each new chunk. It is also
|
||||
* not a threadlocal because it's held within a threadlocal BlockRenderer.
|
||||
*/
|
||||
private final Long2IntOpenHashMap brightnessCache;
|
||||
private final Long2FloatOpenHashMap aoLevelCache;
|
||||
|
||||
private final BlockRenderInfo blockInfo;
|
||||
private final BlockPos.Mutable chunkOrigin = new BlockPos.Mutable();
|
||||
AccessChunkRendererData chunkData;
|
||||
ChunkRenderer chunkRenderer;
|
||||
BlockLayeredBufferBuilder builders;
|
||||
BlockRenderView blockView;
|
||||
|
||||
private final Object2ObjectOpenHashMap<BlockRenderLayer, AccessBufferBuilder> buffers = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
private double chunkOffsetX;
|
||||
private double chunkOffsetY;
|
||||
private double chunkOffsetZ;
|
||||
|
||||
// chunk offset + block pos offset + model offsets for plants, etc.
|
||||
private float offsetX = 0;
|
||||
private float offsetY = 0;
|
||||
private float offsetZ = 0;
|
||||
|
||||
ChunkRenderInfo(BlockRenderInfo blockInfo) {
|
||||
this.blockInfo = blockInfo;
|
||||
brightnessCache = new Long2IntOpenHashMap();
|
||||
brightnessCache.defaultReturnValue(Integer.MAX_VALUE);
|
||||
aoLevelCache = new Long2FloatOpenHashMap();
|
||||
aoLevelCache.defaultReturnValue(Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
void prepare(ChunkRendererRegion blockView, ChunkRenderer chunkRenderer, ChunkRenderData chunkData, BlockLayeredBufferBuilder builders) {
|
||||
this.blockView = blockView;
|
||||
this.chunkOrigin.set(chunkRenderer.getOrigin());
|
||||
this.chunkData = (AccessChunkRendererData) chunkData;
|
||||
this.chunkRenderer = chunkRenderer;
|
||||
this.builders = builders;
|
||||
buffers.clear();
|
||||
chunkOffsetX = -chunkOrigin.getX();
|
||||
chunkOffsetY = -chunkOrigin.getY();
|
||||
chunkOffsetZ = -chunkOrigin.getZ();
|
||||
brightnessCache.clear();
|
||||
aoLevelCache.clear();
|
||||
}
|
||||
|
||||
void release() {
|
||||
chunkData = null;
|
||||
chunkRenderer = null;
|
||||
buffers.clear();
|
||||
}
|
||||
|
||||
void beginBlock() {
|
||||
final BlockState blockState = blockInfo.blockState;
|
||||
final BlockPos blockPos = blockInfo.blockPos;
|
||||
|
||||
// When we are using the BufferBuilder input methods, the builder will
|
||||
// add the chunk offset for us, so we should only apply the block offset.
|
||||
if (Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
offsetX = (float) (blockPos.getX());
|
||||
offsetY = (float) (blockPos.getY());
|
||||
offsetZ = (float) (blockPos.getZ());
|
||||
} else {
|
||||
offsetX = (float) (chunkOffsetX + blockPos.getX());
|
||||
offsetY = (float) (chunkOffsetY + blockPos.getY());
|
||||
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
|
||||
}
|
||||
|
||||
if (blockState.getBlock().getOffsetType() != OffsetType.NONE) {
|
||||
Vec3d offset = blockState.getOffsetPos(blockInfo.blockView, blockPos);
|
||||
offsetX += (float) offset.x;
|
||||
offsetY += (float) offset.y;
|
||||
offsetZ += (float) offset.z;
|
||||
}
|
||||
}
|
||||
|
||||
/** Lazily retrieves output buffer for given layer, initializing as needed. */
|
||||
public AccessBufferBuilder getInitializedBuffer(BlockRenderLayer renderLayer) {
|
||||
AccessBufferBuilder result = buffers.get(renderLayer);
|
||||
|
||||
if (result == null) {
|
||||
BufferBuilder builder = builders.get(renderLayer);
|
||||
result = (AccessBufferBuilder) builder;
|
||||
chunkData.fabric_markPopulated(renderLayer);
|
||||
buffers.put(renderLayer, result);
|
||||
|
||||
if (chunkData.fabric_markInitialized(renderLayer)) {
|
||||
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, chunkOrigin);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies position offset for chunk and, if present, block random offset.
|
||||
*/
|
||||
void applyOffsets(MutableQuadViewImpl q) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q.pos(i, q.x(i) + offsetX, q.y(i) + offsetY, q.z(i) + offsetZ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached values for {@link BlockState#getBlockBrightness(BlockRenderView, BlockPos)}.
|
||||
* See also the comments for {@link #brightnessCache}.
|
||||
*/
|
||||
int cachedBrightness(BlockPos pos) {
|
||||
long key = pos.asLong();
|
||||
int result = brightnessCache.get(key);
|
||||
|
||||
if (result == Integer.MAX_VALUE) {
|
||||
result = blockView.getLightmapIndex(blockView.getBlockState(pos), pos);
|
||||
brightnessCache.put(key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float cachedAoLevel(BlockPos pos) {
|
||||
long key = pos.asLong();
|
||||
float result = aoLevelCache.get(key);
|
||||
|
||||
if (result == Float.MAX_VALUE) {
|
||||
result = AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
aoLevelCache.put(key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,25 +17,27 @@
|
|||
package net.fabricmc.indigo.renderer.render;
|
||||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
|
||||
/**
|
||||
* Controls 1x warning for vanilla quad vertex format when running in compatibility mode.
|
||||
*/
|
||||
public abstract class CompatibilityHelper {
|
||||
private CompatibilityHelper() {}
|
||||
private CompatibilityHelper() { }
|
||||
|
||||
private static boolean logCompatibilityWarning = true;
|
||||
|
||||
private static boolean isCompatible(int[] vertexData) {
|
||||
final boolean result = vertexData.length == 28;
|
||||
if(!result && logCompatibilityWarning) {
|
||||
logCompatibilityWarning = false;
|
||||
Indigo.LOGGER.warn("[Indigo] Encountered baked quad with non-standard vertex format. Some blocks will not be rendered");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean canRender(int[] vertexData) {
|
||||
return !Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY || isCompatible(vertexData);
|
||||
}
|
||||
private static boolean logCompatibilityWarning = true;
|
||||
|
||||
private static boolean isCompatible(int[] vertexData) {
|
||||
final boolean result = vertexData.length == EncodingFormat.QUAD_STRIDE;
|
||||
|
||||
if (!result && logCompatibilityWarning) {
|
||||
logCompatibilityWarning = false;
|
||||
Indigo.LOGGER.warn("[Indigo] Encountered baked quad with non-standard vertex format. Some blocks will not be rendered");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean canRender(int[] vertexData) {
|
||||
return !Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY || isCompatible(vertexData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.indigo.renderer.render;
|
||||
|
||||
import static net.fabricmc.indigo.renderer.render.AbstractQuadRenderer.FULL_BRIGHTNESS;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -52,223 +54,219 @@ import net.minecraft.util.math.Direction;
|
|||
* of simplicity in the default renderer.
|
||||
*/
|
||||
public class ItemRenderContext extends AbstractRenderContext implements RenderContext {
|
||||
/** used to accept a method reference from the ItemRenderer */
|
||||
@FunctionalInterface
|
||||
public static interface VanillaQuadHandler {
|
||||
void accept(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack);
|
||||
}
|
||||
/** Value vanilla uses for item rendering. The only sensible choice, of course. */
|
||||
private static final long ITEM_RANDOM_SEED = 42L;
|
||||
|
||||
private final ItemColors colorMap;
|
||||
private final Random random = new Random();
|
||||
private final Consumer<BakedModel> fallbackConsumer;
|
||||
BufferBuilder bufferBuilder;
|
||||
AccessBufferBuilder fabricBuffer;
|
||||
private int color;
|
||||
private ItemStack itemStack;
|
||||
private VanillaQuadHandler vanillaHandler;
|
||||
private boolean smoothShading = false;
|
||||
private boolean enchantment = false;
|
||||
|
||||
private final Supplier<Random> randomSupplier = () -> {
|
||||
Random result = random;
|
||||
result.setSeed(42L);
|
||||
return random;
|
||||
};
|
||||
|
||||
/**
|
||||
* When rendering an enchanted item, input stack will be empty.
|
||||
* This value is populated earlier in the call tree when this is the case
|
||||
* so that we can render correct geometry and only a single texture.
|
||||
*/
|
||||
public ItemStack enchantmentStack;
|
||||
|
||||
private final int[] quadData = new int[EncodingFormat.MAX_STRIDE];;
|
||||
|
||||
public ItemRenderContext(ItemColors colorMap) {
|
||||
this.colorMap = colorMap;
|
||||
this.fallbackConsumer = this::fallbackConsumer;
|
||||
}
|
||||
|
||||
public void renderModel(FabricBakedModel model, int color, ItemStack stack, VanillaQuadHandler vanillaHandler) {
|
||||
this.color = color;
|
||||
|
||||
if (stack.isEmpty() && enchantmentStack != null) {
|
||||
enchantment = true;
|
||||
this.itemStack = enchantmentStack;
|
||||
enchantmentStack = null;
|
||||
} else {
|
||||
enchantment = false;
|
||||
this.itemStack = stack;
|
||||
}
|
||||
|
||||
this.vanillaHandler = vanillaHandler;
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
bufferBuilder = tessellator.getBufferBuilder();
|
||||
fabricBuffer = (AccessBufferBuilder)this.bufferBuilder;
|
||||
|
||||
bufferBuilder.begin(7, VertexFormats.POSITION_COLOR_UV_NORMAL);
|
||||
model.emitItemQuads(stack, randomSupplier, this);
|
||||
tessellator.draw();
|
||||
|
||||
if (smoothShading) {
|
||||
RenderSystem.shadeModel(GL11.GL_FLAT);
|
||||
smoothShading = false;
|
||||
}
|
||||
|
||||
bufferBuilder = null;
|
||||
fabricBuffer = null;
|
||||
tessellator = null;
|
||||
this.itemStack = null;
|
||||
this.vanillaHandler = null;
|
||||
}
|
||||
/** used to accept a method reference from the ItemRenderer */
|
||||
@FunctionalInterface
|
||||
public static interface VanillaQuadHandler {
|
||||
void accept(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack);
|
||||
}
|
||||
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
{
|
||||
data = quadData;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace = GeometryHelper.lightFace(this);
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad();
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private final Maker editorQuad = new Maker();
|
||||
|
||||
private final Consumer<Mesh> meshConsumer = (mesh) -> {
|
||||
MeshImpl m = (MeshImpl)mesh;
|
||||
final int[] data = m.data();
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
while (index < limit) {
|
||||
RenderMaterialImpl.Value mat = RenderMaterialImpl.byIndex(data[index]);
|
||||
final int stride = EncodingFormat.stride(mat.spriteDepth());
|
||||
System.arraycopy(data, index, editorQuad.data(), 0, stride);
|
||||
editorQuad.load();
|
||||
index += stride;
|
||||
renderQuad();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Vanilla normally renders items with flat shading - meaning only
|
||||
* the last vertex normal is applied for lighting purposes. We
|
||||
* support non-cube vertex normals so we need to change this to smooth
|
||||
* for models that use them. We don't change it unless needed because
|
||||
* OpenGL state changes always impose a performance cost and this happens
|
||||
* for every item, every frame.
|
||||
*/
|
||||
private void handleShading() {
|
||||
if (!smoothShading && editorQuad.hasVertexNormals()) {
|
||||
smoothShading = true;
|
||||
RenderSystem.shadeModel(GL11.GL_SMOOTH);
|
||||
}
|
||||
}
|
||||
|
||||
private int quadColor() {
|
||||
final int colorIndex = editorQuad.colorIndex();
|
||||
int quadColor = color;
|
||||
if (!enchantment && quadColor == -1 && colorIndex != -1) {
|
||||
quadColor = colorMap.getColorMultiplier(itemStack, colorIndex);
|
||||
quadColor |= -16777216;
|
||||
}
|
||||
return quadColor;
|
||||
}
|
||||
|
||||
private void colorizeAndOutput(int quadColor) {
|
||||
final MutableQuadViewImpl q = editorQuad;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int c = q.spriteColor(i, 0);
|
||||
c = ColorHelper.multiplyColor(quadColor, c);
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(c));
|
||||
}
|
||||
fabricBuffer.fabric_putQuad(q);
|
||||
}
|
||||
|
||||
private void renderQuad() {
|
||||
final MutableQuadViewImpl quad = editorQuad;
|
||||
if (!transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderMaterialImpl.Value mat = quad.material();
|
||||
final int quadColor = quadColor();
|
||||
final int textureCount = mat.spriteDepth();
|
||||
|
||||
handleShading();
|
||||
|
||||
quad.populateMissingNormals();
|
||||
quad.lightmap(0xF000F0, 0xF000F0, 0xF000F0, 0xF000F0);
|
||||
|
||||
colorizeAndOutput(!enchantment && mat.disableColorIndex(0) ? -1 : quadColor);
|
||||
|
||||
// no need to render additional textures for enchantment overlay
|
||||
if (!enchantment && textureCount > 1) {
|
||||
quad.copyColorUV(1, quadData, EncodingFormat.VERTEX_START_OFFSET);
|
||||
colorizeAndOutput(mat.disableColorIndex(1) ? -1 : quadColor);
|
||||
|
||||
if (textureCount == 3) {
|
||||
quad.copyColorUV(2, quadData, EncodingFormat.VERTEX_START_OFFSET);
|
||||
colorizeAndOutput(mat.disableColorIndex(2) ? -1 : quadColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
private final ItemColors colorMap;
|
||||
private final Random random = new Random();
|
||||
private final Consumer<BakedModel> fallbackConsumer;
|
||||
BufferBuilder bufferBuilder;
|
||||
AccessBufferBuilder fabricBuffer;
|
||||
private int color;
|
||||
private ItemStack itemStack;
|
||||
private VanillaQuadHandler vanillaHandler;
|
||||
private boolean smoothShading = false;
|
||||
private boolean enchantment = false;
|
||||
|
||||
private void fallbackConsumer(BakedModel model) {
|
||||
if (hasTransform()) {
|
||||
// if there's a transform in effect, convert to mesh-based quads so that we can apply it
|
||||
for (int i = 0; i < 7; i++) {
|
||||
random.setSeed(42L);
|
||||
final Direction cullFace = ModelHelper.faceFromIndex(i);
|
||||
renderFallbackWithTransform(bufferBuilder, model.getQuads((BlockState)null, cullFace, random), color, itemStack, cullFace);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
random.setSeed(42L);
|
||||
vanillaHandler.accept(bufferBuilder, model.getQuads((BlockState)null, ModelHelper.faceFromIndex(i), random), color, itemStack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void renderFallbackWithTransform(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack, Direction cullFace) {
|
||||
if (quads.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (CompatibilityHelper.canRender(quads.get(0).getVertexData())) {
|
||||
Maker editorQuad = this.editorQuad;
|
||||
for (BakedQuad q : quads) {
|
||||
editorQuad.clear();
|
||||
editorQuad.fromVanilla(q.getVertexData(), 0, false);
|
||||
editorQuad.cullFace(cullFace);
|
||||
final Direction lightFace = q.getFace();
|
||||
editorQuad.lightFace(lightFace);
|
||||
editorQuad.nominalFace(lightFace);
|
||||
editorQuad.colorIndex(q.getColorIndex());
|
||||
renderQuad();
|
||||
}
|
||||
} else {
|
||||
vanillaHandler.accept(bufferBuilder, quads, color, stack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return fallbackConsumer;
|
||||
}
|
||||
private final Supplier<Random> randomSupplier = () -> {
|
||||
Random result = random;
|
||||
result.setSeed(ITEM_RANDOM_SEED);
|
||||
return random;
|
||||
};
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
editorQuad.clear();
|
||||
return editorQuad;
|
||||
}
|
||||
/**
|
||||
* When rendering an enchanted item, input stack will be empty.
|
||||
* This value is populated earlier in the call tree when this is the case
|
||||
* so that we can render correct geometry and only a single texture.
|
||||
*/
|
||||
public ItemStack enchantmentStack;
|
||||
|
||||
private final int[] quadData = new int[EncodingFormat.TOTAL_STRIDE];;
|
||||
|
||||
public ItemRenderContext(ItemColors colorMap) {
|
||||
this.colorMap = colorMap;
|
||||
this.fallbackConsumer = this::fallbackConsumer;
|
||||
}
|
||||
|
||||
public void renderModel(FabricBakedModel model, int color, ItemStack stack, VanillaQuadHandler vanillaHandler) {
|
||||
this.color = color;
|
||||
|
||||
if (stack.isEmpty() && enchantmentStack != null) {
|
||||
enchantment = true;
|
||||
this.itemStack = enchantmentStack;
|
||||
enchantmentStack = null;
|
||||
} else {
|
||||
enchantment = false;
|
||||
this.itemStack = stack;
|
||||
}
|
||||
|
||||
this.vanillaHandler = vanillaHandler;
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
bufferBuilder = tessellator.getBufferBuilder();
|
||||
fabricBuffer = (AccessBufferBuilder) this.bufferBuilder;
|
||||
|
||||
bufferBuilder.begin(7, VertexFormats.POSITION_COLOR_UV_NORMAL);
|
||||
model.emitItemQuads(stack, randomSupplier, this);
|
||||
tessellator.draw();
|
||||
|
||||
if (smoothShading) {
|
||||
RenderSystem.shadeModel(GL11.GL_FLAT);
|
||||
smoothShading = false;
|
||||
}
|
||||
|
||||
bufferBuilder = null;
|
||||
fabricBuffer = null;
|
||||
tessellator = null;
|
||||
this.itemStack = null;
|
||||
this.vanillaHandler = null;
|
||||
}
|
||||
|
||||
private class Maker extends MutableQuadViewImpl implements QuadEmitter {
|
||||
{
|
||||
data = quadData;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace(GeometryHelper.lightFace(this));
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad();
|
||||
clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private final Maker editorQuad = new Maker();
|
||||
|
||||
private final Consumer<Mesh> meshConsumer = (mesh) -> {
|
||||
MeshImpl m = (MeshImpl) mesh;
|
||||
final int[] data = m.data();
|
||||
final int limit = data.length;
|
||||
int index = 0;
|
||||
|
||||
while (index < limit) {
|
||||
System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE);
|
||||
editorQuad.load();
|
||||
index += EncodingFormat.TOTAL_STRIDE;
|
||||
renderQuad();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Vanilla normally renders items with flat shading - meaning only
|
||||
* the last vertex normal is applied for lighting purposes. We
|
||||
* support non-cube vertex normals so we need to change this to smooth
|
||||
* for models that use them. We don't change it unless needed because
|
||||
* OpenGL state changes always impose a performance cost and this happens
|
||||
* for every item, every frame.
|
||||
*/
|
||||
private void handleShading() {
|
||||
if (!smoothShading && editorQuad.hasVertexNormals()) {
|
||||
smoothShading = true;
|
||||
RenderSystem.shadeModel(GL11.GL_SMOOTH);
|
||||
}
|
||||
}
|
||||
|
||||
private int quadColor() {
|
||||
final int colorIndex = editorQuad.colorIndex();
|
||||
int quadColor = color;
|
||||
|
||||
if (!enchantment && quadColor == -1 && colorIndex != -1) {
|
||||
quadColor = colorMap.getColorMultiplier(itemStack, colorIndex);
|
||||
quadColor |= 0xFF000000;
|
||||
}
|
||||
return quadColor;
|
||||
}
|
||||
|
||||
private void colorizeAndOutput(int quadColor) {
|
||||
final MutableQuadViewImpl q = editorQuad;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int c = q.spriteColor(i, 0);
|
||||
c = ColorHelper.multiplyColor(quadColor, c);
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(c));
|
||||
}
|
||||
|
||||
fabricBuffer.fabric_putQuad(q);
|
||||
}
|
||||
|
||||
private void renderQuad() {
|
||||
final MutableQuadViewImpl quad = editorQuad;
|
||||
|
||||
if (!transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderMaterialImpl.Value mat = quad.material();
|
||||
final int quadColor = quadColor();
|
||||
|
||||
handleShading();
|
||||
|
||||
quad.populateMissingNormals();
|
||||
quad.lightmap(FULL_BRIGHTNESS, FULL_BRIGHTNESS, FULL_BRIGHTNESS, FULL_BRIGHTNESS);
|
||||
|
||||
colorizeAndOutput(!enchantment && mat.disableColorIndex(0) ? -1 : quadColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
|
||||
private void fallbackConsumer(BakedModel model) {
|
||||
if (hasTransform()) {
|
||||
// if there's a transform in effect, convert to mesh-based quads so that we can apply it
|
||||
for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
|
||||
random.setSeed(ITEM_RANDOM_SEED);
|
||||
final Direction cullFace = ModelHelper.faceFromIndex(i);
|
||||
renderFallbackWithTransform(bufferBuilder, model.getQuads((BlockState) null, cullFace, random), color, itemStack, cullFace);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
|
||||
random.setSeed(ITEM_RANDOM_SEED);
|
||||
vanillaHandler.accept(bufferBuilder, model.getQuads((BlockState) null, ModelHelper.faceFromIndex(i), random), color, itemStack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void renderFallbackWithTransform(BufferBuilder bufferBuilder, List<BakedQuad> quads, int color, ItemStack stack, Direction cullFace) {
|
||||
if (quads.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CompatibilityHelper.canRender(quads.get(0).getVertexData())) {
|
||||
Maker editorQuad = this.editorQuad;
|
||||
|
||||
for (BakedQuad q : quads) {
|
||||
editorQuad.clear();
|
||||
editorQuad.fromVanilla(q.getVertexData(), 0, false);
|
||||
editorQuad.cullFace(cullFace);
|
||||
final Direction lightFace = q.getFace();
|
||||
editorQuad.lightFace(lightFace);
|
||||
editorQuad.nominalFace(lightFace);
|
||||
editorQuad.colorIndex(q.getColorIndex());
|
||||
renderQuad();
|
||||
}
|
||||
} else {
|
||||
vanillaHandler.accept(bufferBuilder, quads, color, stack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return fallbackConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
editorQuad.clear();
|
||||
return editorQuad;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,34 +22,35 @@ import net.minecraft.util.math.BlockPos;
|
|||
import net.minecraft.util.math.Direction;
|
||||
|
||||
public class TerrainBlockRenderInfo extends BlockRenderInfo {
|
||||
private int cullCompletionFlags;
|
||||
private int cullResultFlags;
|
||||
|
||||
|
||||
@Override
|
||||
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
|
||||
super.prepareForBlock(blockState, blockPos, modelAO);
|
||||
cullCompletionFlags = 0;
|
||||
cullResultFlags = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldDrawFace(Direction face) {
|
||||
if(face == null) {
|
||||
return true;
|
||||
}
|
||||
final int mask = 1 << face.getId();
|
||||
|
||||
if((cullCompletionFlags & mask) == 0) {
|
||||
cullCompletionFlags |= mask;
|
||||
if(Block.shouldDrawSide(blockState, blockView, blockPos, face)) {
|
||||
cullResultFlags |= mask;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return (cullResultFlags & mask) != 0;
|
||||
}
|
||||
}
|
||||
private int cullCompletionFlags;
|
||||
private int cullResultFlags;
|
||||
|
||||
@Override
|
||||
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
|
||||
super.prepareForBlock(blockState, blockPos, modelAO);
|
||||
cullCompletionFlags = 0;
|
||||
cullResultFlags = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldDrawFace(Direction face) {
|
||||
if (face == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int mask = 1 << face.getId();
|
||||
|
||||
if ((cullCompletionFlags & mask) == 0) {
|
||||
cullCompletionFlags |= mask;
|
||||
|
||||
if (Block.shouldDrawSide(blockState, blockView, blockPos, face)) {
|
||||
cullResultFlags |= mask;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return (cullResultFlags & mask) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,97 +55,99 @@ import net.minecraft.util.math.Direction;
|
|||
* manipulating the data via NIO.
|
||||
*/
|
||||
public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Consumer<BakedModel> {
|
||||
private static Value MATERIAL_FLAT = (Value) IndigoRenderer.INSTANCE.materialFinder().disableAo(0, true).find();
|
||||
private static Value MATERIAL_SHADED = (Value) IndigoRenderer.INSTANCE.materialFinder().find();
|
||||
|
||||
private final int[] editorBuffer = new int[32];
|
||||
private final ChunkRenderInfo chunkInfo;
|
||||
|
||||
TerrainFallbackConsumer(BlockRenderInfo blockInfo, ChunkRenderInfo chunkInfo, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, transform);
|
||||
this.chunkInfo = chunkInfo;
|
||||
}
|
||||
|
||||
private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
|
||||
{
|
||||
data = editorBuffer;
|
||||
material = MATERIAL_SHADED;
|
||||
baseIndex = -EncodingFormat.HEADER_STRIDE;
|
||||
}
|
||||
private static Value MATERIAL_FLAT = (Value) IndigoRenderer.INSTANCE.materialFinder().disableAo(0, true).find();
|
||||
private static Value MATERIAL_SHADED = (Value) IndigoRenderer.INSTANCE.materialFinder().find();
|
||||
|
||||
@Override
|
||||
public QuadEmitter emit() {
|
||||
// should not be called
|
||||
throw new UnsupportedOperationException("Fallback consumer does not support .emit()");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void accept(BakedModel model) {
|
||||
final Supplier<Random> random = blockInfo.randomSupplier;
|
||||
final Value defaultMaterial = blockInfo.defaultAo && model.useAmbientOcclusion()
|
||||
? MATERIAL_SHADED : MATERIAL_FLAT;
|
||||
final BlockState blockState = blockInfo.blockState;
|
||||
for(int i = 0; i < 6; i++) {
|
||||
Direction face = ModelHelper.faceFromIndex(i);
|
||||
List<BakedQuad> quads = model.getQuads(blockState, face, random.get());
|
||||
final int count = quads.size();
|
||||
if (count != 0 && blockInfo.shouldDrawFace(face)) {
|
||||
for(int j = 0; j < count; j++) {
|
||||
BakedQuad q = quads.get(j);
|
||||
renderQuad(q, face, defaultMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
private final int[] editorBuffer = new int[EncodingFormat.TOTAL_STRIDE];
|
||||
private final ChunkRenderInfo chunkInfo;
|
||||
|
||||
List<BakedQuad> quads = model.getQuads(blockState, null, random.get());
|
||||
final int count = quads.size();
|
||||
if (count != 0) {
|
||||
for(int j = 0; j < count; j++) {
|
||||
BakedQuad q = quads.get(j);
|
||||
renderQuad(q, null, defaultMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderQuad(BakedQuad quad, Direction cullFace, Value defaultMaterial) {
|
||||
final int[] vertexData = quad.getVertexData();
|
||||
if (!CompatibilityHelper.canRender(vertexData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MutableQuadViewImpl editorQuad = this.editorQuad;
|
||||
System.arraycopy(vertexData, 0, editorBuffer, 0, 32);
|
||||
editorQuad.cullFace(cullFace);
|
||||
final Direction lightFace = quad.getFace();
|
||||
editorQuad.lightFace(lightFace);
|
||||
editorQuad.nominalFace(lightFace);
|
||||
editorQuad.colorIndex(quad.getColorIndex());
|
||||
editorQuad.material(defaultMaterial);
|
||||
|
||||
if (!transform.transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editorQuad.material().hasAo) {
|
||||
// needs to happen before offsets are applied
|
||||
editorQuad.invalidateShape();
|
||||
aoCalc.compute(editorQuad, true);
|
||||
chunkInfo.applyOffsets(editorQuad);
|
||||
tesselateSmooth(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
|
||||
} else {
|
||||
// vanilla compatibility hack
|
||||
TerrainFallbackConsumer(BlockRenderInfo blockInfo, ChunkRenderInfo chunkInfo, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, transform);
|
||||
this.chunkInfo = chunkInfo;
|
||||
}
|
||||
|
||||
private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
|
||||
{
|
||||
data = editorBuffer;
|
||||
material(MATERIAL_SHADED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter emit() {
|
||||
// should not be called
|
||||
throw new UnsupportedOperationException("Fallback consumer does not support .emit()");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void accept(BakedModel model) {
|
||||
final Supplier<Random> random = blockInfo.randomSupplier;
|
||||
final Value defaultMaterial = blockInfo.defaultAo && model.useAmbientOcclusion() ? MATERIAL_SHADED : MATERIAL_FLAT;
|
||||
final BlockState blockState = blockInfo.blockState;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Direction face = ModelHelper.faceFromIndex(i);
|
||||
List<BakedQuad> quads = model.getQuads(blockState, face, random.get());
|
||||
final int count = quads.size();
|
||||
|
||||
if (count != 0 && blockInfo.shouldDrawFace(face)) {
|
||||
for (int j = 0; j < count; j++) {
|
||||
BakedQuad q = quads.get(j);
|
||||
renderQuad(q, face, defaultMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<BakedQuad> quads = model.getQuads(blockState, null, random.get());
|
||||
final int count = quads.size();
|
||||
|
||||
if (count != 0) {
|
||||
for (int j = 0; j < count; j++) {
|
||||
BakedQuad q = quads.get(j);
|
||||
renderQuad(q, null, defaultMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderQuad(BakedQuad quad, Direction cullFace, Value defaultMaterial) {
|
||||
final int[] vertexData = quad.getVertexData();
|
||||
|
||||
if (!CompatibilityHelper.canRender(vertexData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MutableQuadViewImpl editorQuad = this.editorQuad;
|
||||
System.arraycopy(vertexData, 0, editorBuffer, EncodingFormat.HEADER_STRIDE, EncodingFormat.QUAD_STRIDE);
|
||||
editorQuad.cullFace(cullFace);
|
||||
final Direction lightFace = quad.getFace();
|
||||
editorQuad.lightFace(lightFace);
|
||||
editorQuad.nominalFace(lightFace);
|
||||
editorQuad.colorIndex(quad.getColorIndex());
|
||||
editorQuad.material(defaultMaterial);
|
||||
|
||||
if (!transform.transform(editorQuad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorQuad.material().disableAo(0)) {
|
||||
// needs to happen before offsets are applied
|
||||
editorQuad.invalidateShape();
|
||||
aoCalc.compute(editorQuad, true);
|
||||
chunkInfo.applyOffsets(editorQuad);
|
||||
tesselateSmooth(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
|
||||
} else {
|
||||
// vanilla compatibility hack
|
||||
// For flat lighting, cull face drives everything and light face is ignored.
|
||||
if(cullFace == null) {
|
||||
editorQuad.invalidateShape();
|
||||
// Can't rely on lazy computation in tesselateFlat() because needs to happen before offsets are applied
|
||||
editorQuad.geometryFlags();
|
||||
} else {
|
||||
editorQuad.geometryFlags(GeometryHelper.LIGHT_FACE_FLAG);
|
||||
editorQuad.lightFace(cullFace);
|
||||
}
|
||||
chunkInfo.applyOffsets(editorQuad);
|
||||
tesselateFlat(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
|
||||
}
|
||||
}
|
||||
if (cullFace == null) {
|
||||
editorQuad.invalidateShape();
|
||||
// Can't rely on lazy computation in tesselateFlat() because needs to happen before offsets are applied
|
||||
editorQuad.geometryFlags();
|
||||
} else {
|
||||
editorQuad.geometryFlags(GeometryHelper.LIGHT_FACE_FLAG);
|
||||
editorQuad.lightFace(cullFace);
|
||||
}
|
||||
chunkInfo.applyOffsets(editorQuad);
|
||||
tesselateFlat(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,15 @@ import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
|||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
|
||||
public class TerrainMeshConsumer extends AbstractMeshConsumer {
|
||||
private final ChunkRenderInfo chunkInfo;
|
||||
TerrainMeshConsumer(TerrainBlockRenderInfo blockInfo, ChunkRenderInfo chunkInfo, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, transform);
|
||||
this.chunkInfo = chunkInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyOffsets(MutableQuadViewImpl quad) {
|
||||
chunkInfo.applyOffsets(quad);
|
||||
}
|
||||
private final ChunkRenderInfo chunkInfo;
|
||||
|
||||
TerrainMeshConsumer(TerrainBlockRenderInfo blockInfo, ChunkRenderInfo chunkInfo, AoCalculator aoCalc, QuadTransform transform) {
|
||||
super(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, transform);
|
||||
this.chunkInfo = chunkInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyOffsets(MutableQuadViewImpl quad) {
|
||||
chunkInfo.applyOffsets(quad);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,57 +40,53 @@ import net.minecraft.util.math.BlockPos;
|
|||
* and holds/manages all of the state needed by them.
|
||||
*/
|
||||
public class TerrainRenderContext extends AbstractRenderContext implements RenderContext {
|
||||
public static final ThreadLocal<TerrainRenderContext> POOL = ThreadLocal.withInitial(TerrainRenderContext::new);
|
||||
private final TerrainBlockRenderInfo blockInfo = new TerrainBlockRenderInfo();
|
||||
private final ChunkRenderInfo chunkInfo = new ChunkRenderInfo(blockInfo);
|
||||
private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
|
||||
private final TerrainMeshConsumer meshConsumer = new TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
|
||||
public TerrainRenderContext prepare(
|
||||
ChunkRendererRegion blockView,
|
||||
ChunkRenderer chunkRenderer,
|
||||
ChunkRenderData chunkData,
|
||||
BlockLayeredBufferBuilder builders) {
|
||||
blockInfo.setBlockView(blockView);
|
||||
chunkInfo.prepare(blockView, chunkRenderer, chunkData, builders);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
chunkInfo.release();
|
||||
blockInfo.release();
|
||||
}
|
||||
|
||||
/** Called from chunk renderer hook. */
|
||||
public boolean tesselateBlock(BlockState blockState, BlockPos blockPos, final BakedModel model) {
|
||||
try {
|
||||
aoCalc.clear();
|
||||
blockInfo.prepareForBlock(blockState, blockPos, model.useAmbientOcclusion());
|
||||
chunkInfo.beginBlock();
|
||||
((FabricBakedModel)model).emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
|
||||
} catch (Throwable var9) {
|
||||
CrashReport crashReport_1 = CrashReport.create(var9, "Tesselating block in world - Indigo Renderer");
|
||||
CrashReportSection crashReportElement_1 = crashReport_1.addElement("Block being tesselated");
|
||||
CrashReportSection.addBlockInfo(crashReportElement_1, blockPos, blockState);
|
||||
throw new CrashException(crashReport_1);
|
||||
}
|
||||
// false because we've already marked the chunk as populated - caller doesn't need to
|
||||
return false;
|
||||
}
|
||||
public static final ThreadLocal<TerrainRenderContext> POOL = ThreadLocal.withInitial(TerrainRenderContext::new);
|
||||
private final TerrainBlockRenderInfo blockInfo = new TerrainBlockRenderInfo();
|
||||
private final ChunkRenderInfo chunkInfo = new ChunkRenderInfo(blockInfo);
|
||||
private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
|
||||
private final TerrainMeshConsumer meshConsumer = new TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
public TerrainRenderContext prepare(ChunkRendererRegion blockView, ChunkRenderer chunkRenderer, ChunkRenderData chunkData, BlockLayeredBufferBuilder builders) {
|
||||
blockInfo.setBlockView(blockView);
|
||||
chunkInfo.prepare(blockView, chunkRenderer, chunkData, builders);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return fallbackConsumer;
|
||||
}
|
||||
public void release() {
|
||||
chunkInfo.release();
|
||||
blockInfo.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
return meshConsumer.getEmitter();
|
||||
}
|
||||
/** Called from chunk renderer hook. */
|
||||
public boolean tesselateBlock(BlockState blockState, BlockPos blockPos, final BakedModel model) {
|
||||
try {
|
||||
aoCalc.clear();
|
||||
blockInfo.prepareForBlock(blockState, blockPos, model.useAmbientOcclusion());
|
||||
chunkInfo.beginBlock();
|
||||
((FabricBakedModel) model).emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
|
||||
} catch (Throwable var9) {
|
||||
CrashReport crashReport_1 = CrashReport.create(var9, "Tesselating block in world - Indigo Renderer");
|
||||
CrashReportSection crashReportElement_1 = crashReport_1.addElement("Block being tesselated");
|
||||
CrashReportSection.addBlockInfo(crashReportElement_1, blockPos, blockState);
|
||||
throw new CrashException(crashReport_1);
|
||||
}
|
||||
// false because we've already marked the chunk as populated - caller doesn't need to
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Mesh> meshConsumer() {
|
||||
return meshConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<BakedModel> fallbackConsumer() {
|
||||
return fallbackConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadEmitter getEmitter() {
|
||||
return meshConsumer.getEmitter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
],
|
||||
"client": [
|
||||
"BufferBuilderOffsetAccessor",
|
||||
"MixinBlockModelRenderer",
|
||||
"MixinAmbientOcclusionCalculator",
|
||||
"MixinBlockModelRenderer",
|
||||
"MixinBufferBuilder",
|
||||
"MixinChunkRebuildTask",
|
||||
"MixinChunkRenderData",
|
||||
"MixinChunkRenderer",
|
||||
"MixinChunkRenderer",
|
||||
"MixinChunkRendererRegion",
|
||||
"MixinItemRenderer"
|
||||
"MixinItemRenderer"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue