1.15 Rendering Cleanup ()

* 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 
* Bump patch versions
* Use unmodified vanilla AO calculator
This commit is contained in:
grondag 2019-09-24 07:27:27 -07:00 committed by GitHub
parent 96946203be
commit a5d709c3e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 4426 additions and 4578 deletions
build.gradle
fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric
fabric-renderer-indigo

View file

@ -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"
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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 &gt; 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 &gt; 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);
}

View file

@ -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 &gt; 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 &gt; 1 is currently undefined.
* See {@link MaterialFinder#spriteDepth(int)}
*/
int spriteDepth();
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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 &gt; 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 &gt; 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);
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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 &lt; or &lt;= {@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 &lt; or &lt;= {@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);
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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')

View file

@ -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.");
}
}
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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 &gt;= 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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -24,8 +24,10 @@ import org.spongepowered.asm.mixin.gen.Accessor;
public interface BufferBuilderOffsetAccessor {
@Accessor
double getOffsetX();
@Accessor
double getOffsetY();
@Accessor
double getOffsetZ();
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -7,15 +7,16 @@
],
"client": [
"BufferBuilderOffsetAccessor",
"MixinBlockModelRenderer",
"MixinAmbientOcclusionCalculator",
"MixinBlockModelRenderer",
"MixinBufferBuilder",
"MixinChunkRebuildTask",
"MixinChunkRenderData",
"MixinChunkRenderer",
"MixinChunkRenderer",
"MixinChunkRendererRegion",
"MixinItemRenderer"
"MixinItemRenderer"
],
"injectors": {
"defaultRequire": 1
}
}
}