[#65] Renderer API + Indigo (#189)

Big thanks to Grondag and Player for all the pain and trouble we all went through.
This commit is contained in:
Adrian Siekierka 2019-05-18 22:25:27 +02:00 committed by GitHub
parent 45e1a1c86e
commit 02a46d5b29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 7125 additions and 0 deletions

View file

@ -0,0 +1,6 @@
{
"schemaVersion": 1,
"id": "fabric-networking",
"version": "${version}",
"license": "Apache-2.0"
}

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-renderer-api-v1"
version = getSubprojectVersion(project, "0.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

View file

@ -0,0 +1,63 @@
/*
* 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.fabric.api.renderer.v1;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.minecraft.util.Identifier;
/**
* Interface for rendering plug-ins that provide enhanced capabilities
* for model lighting, buffering and rendering. Such plug-ins implement the
* 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();
/**
* 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

@ -0,0 +1,46 @@
/*
* 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.fabric.api.renderer.v1;
import net.fabricmc.fabric.impl.renderer.RendererAccessImpl;
/**
* Registration and access for rendering extensions.
*/
public interface RendererAccess {
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);
/**
* 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();
}

View file

@ -0,0 +1,96 @@
/*
* 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.fabric.api.renderer.v1.material;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderLayer;
/**
* Finds standard {@link RenderMaterial} instances used to communicate
* quad rendering characteristics to a {@link RenderContext}.<p>
*
* 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();
/**
* Resets this instance to default values. Values will match those
* in effect when an instance is newly obtained via {@link Renderer#materialFinder()}.
*/
MaterialFinder clear();
/**
*
* Reserved for future use. Behavior for values > 1 is currently undefined.
*/
MaterialFinder spriteDepth(int depth);
/**
* Defines how sprite pixels will be blended with the scene.
* Accepts {link @BlockRenderLayer} values and blending behavior
* will emulate the way that Minecraft renders each pass. But this does
* NOT mean the sprite will be rendered in a specific render pass - some
* implementations may not use the standard Minecraft render passes.<p>
*
* CAN be null and is null by default. A null value means the renderer
* will use {@link Block#getRenderLayer()} for the associate block, or
* {@link BlockRenderLayer#TRANSLUCENT} for item renders. (Normal Minecraft rendering)
*/
MaterialFinder blendMode(int spriteIndex, BlockRenderLayer 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

@ -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.fabric.api.renderer.v1.material;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.minecraft.block.Block;
import net.minecraft.util.Identifier;
/**
* All model quads have an associated render material governing
* how the quad will be rendered.<p>
*
* A material instance is always immutable and thread-safe. References to a material
* remain valid until the end of the current game session.<p>
*
* Materials can be registered and shared between mods using {@link Renderer#registerMaterial(net.minecraft.util.Identifier, RenderMaterial)}.
* The registering mod is responsible for creating each registered material at startup.<p>
*
* Materials are not required to know their registration identity, and two materials
* with the same attributes may or may not satisfy equality and identity tests. Model
* implementations should never attempt to analyze materials or implement control logic based on them.
* They are only tokens for communicating quad attributes to the ModelRenderer.<p>
*
* There are three classes of materials... <p>
*
* <b>STANDARD MATERIALS</b><p>
*
* Standard materials have "normal" rendering with control over lighting,
* color, and texture blending. In the default renderer, "normal" rendering
* emulates unmodified Minecraft. Other renderers may offer a different aesthetic.<p>
*
* The number of standard materials is finite, but not necessarily small.
* To find a standard material, use {@link Renderer#materialFinder()}.<p>
*
* All renderer implementations should support standard materials.<p>
*
* <b>SPECIAL MATERIALS</b><p>
*
* Special materials are implemented directly by the Renderer implementation, typically
* with the aim of providing advanced/extended features. Such materials may offer additional
* vertex attributes via extensions to {@link MeshBuilder} and {@link MutableQuadView}.<p>
*
* Special materials can be obtained using {@link Renderer#materialById(Identifier)}
* with a known identifier. Renderers may provide other means of access. Popular
* special materials could be implemented by multiple renderers, however there is
* no requirement that special materials be cross-compatible.
*/
public interface RenderMaterial {
/**
* This will be identical to the material that would be obtained by calling {@link MaterialFinder#find()}
* on a new, unaltered, {@link MaterialFinder} instance. It is defined here for clarity and convenience.
*
* Quads using this material use {@link Block#getRenderLayer()} of the associated block to determine texture blending,
* honor block color index, are non-emissive, and apply both diffuse and ambient occlusion shading to vertex colors.<p>
*
* All standard, non-fluid baked models are rendered using this material.
*/
Identifier MATERIAL_STANDARD = new Identifier("fabric", "standard");
/**
* How many sprite color/uv coordinates are in the material.
* Behavior for values > 1 is currently undefined.
* See {@link MaterialFinder#spriteDepth(int)}
*/
int spriteDepth();
}

View file

@ -0,0 +1,40 @@
/*
* 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.fabric.api.renderer.v1.mesh;
import java.util.function.Consumer;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
/**
* A bundle of one or more {@link QuadView} instances encoded by the renderer,
* typically via {@link Renderer#meshBuilder()}.<p>
*
* Similar in purpose to the List<BakedQuad> instances returned by BakedModel, but
* affords the renderer the ability to optimize the format for performance
* and memory allocation.<p>
*
* 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);
}

View file

@ -0,0 +1,42 @@
/*
* 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.fabric.api.renderer.v1.mesh;
import net.minecraft.client.render.BufferBuilder;
/**
* Similar in purpose to {@link BufferBuilder} but simpler
* and not tied to NIO or any other specific implementation,
* plus designed to handle both static and dynamic building.<p>
*
* Decouples models from the vertex format(s) used by
* 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();
}

View file

@ -0,0 +1,240 @@
/*
* 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.fabric.api.renderer.v1.mesh;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* A mutable {@link QuadView} instance. The base interface for
* {@link QuadEmitter} and for dynamic renders/mesh transforms.<p>
*
* Instances of {@link MutableQuadView} will practically always be
* threadlocal and/or reused - do not retain references.<p>
*
* 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(float, float, float)} but accepts vector type.
*/
default MutableQuadView pos(int vertexIndex, Vector3f vec) {
return pos(vertexIndex, vec.x(), vec.y(), vec.z());
}
/**
* 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(float, float, float, extra)} but accepts vector type.
*/
default MutableQuadView normal(int vertexIndex, Vector3f vec) {
return normal(vertexIndex, vec.x(), vec.y(), vec.z());
}
/**
* 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 spriteIndex values > 0 is currently undefined.
*/
MutableQuadView spriteColor(int vertexIndex, int spriteIndex, int color);
/**
* Convenience: set sprite color for all vertices at once. Behavior for spriteIndex values > 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 spriteIndex values > 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 spriteIndex values > 0 is currently undefined.
*/
MutableQuadView spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
}

View file

@ -0,0 +1,161 @@
/*
* 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.fabric.api.renderer.v1.mesh;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* Specialized {@link MutableQuadView} obtained via {@link MeshBuilder#getEmitter()}
* to append quads during mesh building.<p>
*
* Also obtained from {@link RenderContext#getEmitter(RenderMaterial)} to submit
* dynamic quads one-by-one at render time.<p>
*
* Instances of {@link QuadEmitter} will practically always be
* threadlocal and/or reused - do not retain references.<p>
*
* Only the renderer should implement or extend this interface.
*/
public interface QuadEmitter extends MutableQuadView {
@Override
QuadEmitter material(RenderMaterial material);
@Override
QuadEmitter cullFace(Direction face);
@Override
QuadEmitter nominalFace(Direction face);
@Override
QuadEmitter colorIndex(int colorIndex);
@Override
QuadEmitter fromVanilla(int[] quadData, int startIndex, boolean isItem);
@Override
QuadEmitter tag(int tag);
@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 normal(int vertexIndex, Vector3f vec) {
MutableQuadView.super.normal(vertexIndex, vec);
return this;
}
@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
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
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);
/**
* 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.)<p>
*
* Square will be parallel to the given face and coplanar with the face if depth == 0.
* All coordinates are normalized (0-1).
*/
default QuadEmitter square(Direction nominalFace, float left, float bottom, float right, float top, float depth) {
cullFace(depth == 0 ? nominalFace : null);
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

@ -0,0 +1,204 @@
/*
* 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.fabric.api.renderer.v1.mesh;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* Interface for reading quad data encoded by {@link MeshBuilder}.
* Enables models to do analysis, re-texturing or translation without knowing the
* renderer's vertex formats and without retaining redundant information.<p>
*
* Only the renderer should implement or extend this interface.
*/
public interface QuadView {
/**
* Reads baked vertex data and outputs standard baked quad
* vertex data in the given array and location.<p>
*
* @param spriteIndex The sprite to be used for the quad.
* Behavior for values > 0 is currently undefined.
*
* @param target Target array for the baked quad data.
*
* @param targetIndex Starting position in target array - array must have
* at least 28 elements available at this index.
*
* @param isItem If true, will output vertex normals. Otherwise will output
* lightmaps, per Minecraft vertex formats for baked models.
*/
void toVanilla(int spriteIndex, int[] target, int targetIndex, boolean isItem);
/**
* Extracts all quad properties except material to the given {@link MutableQuadView} instance.
* Must be used before calling {@link MutableQuadView#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 values > 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.
* See {@link VertexEditor#pos(float, float, float)}
*/
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.
* See {@link VertexEditor#normal(float, float, float, float)}
*/
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

@ -0,0 +1,124 @@
/*
* 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.fabric.api.renderer.v1.model;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Interface for baked models that output meshes with enhanced rendering features.
* Can also be used to generate or customize outputs based on world state instead of
* or in addition to block state when render chunks are rebuilt.<p>
*
* Note for {@link Renderer} implementors: Fabric causes BakedModel to extend this
* interface with {@link #isVanillaAdapter()} == true and to produce standard vertex data.
* 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>
* 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#fallbackModelConsumer()} which handles
* re-seeding per face automatically.<p>
*
* @param Access to world state. Using {@link TerrainBlockView#getCachedRenderData(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(ExtendedBlockView 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 #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 #getItemPropertyOverrides()}
* as vanilla baked models.
*/
void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context);
}

View file

@ -0,0 +1,93 @@
/*
* 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.fabric.api.renderer.v1.model;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.impl.renderer.DamageModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelItemPropertyOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.ExtendedBlockView;
/**
* Base class for specialized model implementations that need to wrap other baked models.
* 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(ExtendedBlockView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
((FabricBakedModel)wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context);
}
@Override
public boolean isVanillaAdapter() {
return ((FabricBakedModel)wrapped).isVanillaAdapter();
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
((FabricBakedModel)wrapped).emitItemQuads(stack, randomSupplier, context);
}
@Override
public List<BakedQuad> getQuads(BlockState blockState, Direction face, Random rand) {
return wrapped.getQuads(blockState, face, rand);
}
@Override
public boolean useAmbientOcclusion() {
return wrapped.useAmbientOcclusion();
}
@Override
public boolean hasDepthInGui() {
return wrapped.hasDepthInGui();
}
@Override
public boolean isBuiltin() {
return wrapped.isBuiltin();
}
@Override
public Sprite getSprite() {
return wrapped.getSprite();
}
@Override
public ModelTransformation getTransformation() {
return wrapped.getTransformation();
}
@Override
public ModelItemPropertyOverrideList getItemPropertyOverrides() {
return wrapped.getItemPropertyOverrides();
}
}

View file

@ -0,0 +1,136 @@
/*
* 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.fabric.api.renderer.v1.model;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.ImmutableList;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.render.model.json.Transformation;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* Collection of utilities for model implementations.
*/
public abstract class ModelHelper {
private ModelHelper() {}
/** Result from {@link #toFaceIndex(Direction)} for null values. */
public static final int NULL_FACE_ID = 6;
/**
* Convenient way to encode faces that may be null.
* Null is returned as {@link #NULL_FACE_ID}.
* Use {@link #faceFromIndex(int)} to retrieve encoded face.
*/
public static int toFaceIndex(Direction face) {
return face == null ? NULL_FACE_ID : face.getId();
}
/**
* Use to decode a result from {@link #toFaceIndex(Direction)}.
* Return value will be null if encoded value was null.
* Can also be used for no-allocation iteration of {@link Direction#values()},
* optionally including the null face. (Use < or <= {@link #NULL_FACE_ID}
* to exclude or include the null value, respectively.)
*/
public static Direction faceFromIndex(int faceIndex) {
return FACES[faceIndex];
}
/** see {@link #faceFromIndex(int)} */
private static final Direction[] FACES = Arrays.copyOf(Direction.values(), 7);
/**
* Converts a mesh into an array of lists of vanilla baked quads.
* Useful for creating vanilla baked models when required for compatibility.
* The array indexes correspond to {@link Direction#getId()} with the
* addition of {@link #NULL_FACE_ID}.<p>
*
* Retrieves sprites from the block texture atlas via {@link SpriteFinder}.
*/
public static List<BakedQuad>[] toQuadLists(Mesh mesh) {
SpriteFinder finder = SpriteFinder.get(MinecraftClient.getInstance().getSpriteAtlas());
@SuppressWarnings("unchecked")
final ImmutableList.Builder<BakedQuad>[] builders = new ImmutableList.Builder[7];
for(int i = 0; i < 7; i++) {
builders[i] = ImmutableList.builder();
}
mesh.forEach(q -> {
final int limit = q.material().spriteDepth();
for(int l = 0; l < limit; l++) {
Direction face = q.cullFace();
builders[face == null ? 6 : face.getId()].add(q.toBakedQuad(l, finder.find(q, l), false));
}
}) ;
@SuppressWarnings("unchecked")
List<BakedQuad>[] result = new List[7];
for(int i = 0; i < 7; i++) {
result[i] = builders[i].build();
}
return result;
}
/**
* 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

@ -0,0 +1,67 @@
/*
* 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.fabric.api.renderer.v1.model;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.impl.renderer.SpriteFinderImpl;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
/**
* Indexes a texture atlas to allow fast lookup of Sprites from
* baked vertex coordinates. Main use is for {@link Mesh}-based models
* to generate vanilla quads on demand without tracking and retaining
* the sprites that were baked into the mesh. In other words, this class
* 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);
/**
* 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

@ -0,0 +1,91 @@
/*
* 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.fabric.api.renderer.v1.render;
import java.util.function.Consumer;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
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(Consumer)}.
* 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

@ -0,0 +1,69 @@
/*
* 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.fabric.impl.renderer;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Specialized model wrapper that implements a general-purpose
* block-breaking render for enhanced models.<p>
*
* Works by intercepting all model output and redirecting to dynamic
* 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(ExtendedBlockView 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

@ -0,0 +1,54 @@
/*
* 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.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();
// 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;
}
}
private Renderer activeRenderer = null;
/** avoids null test every call to {@link #hasRenderer()} */
private boolean hasActiveRenderer = false;
@Override
public final Renderer getRenderer() {
return activeRenderer;
}
@Override
public final boolean hasRenderer() {
return hasActiveRenderer;
}
}

View file

@ -0,0 +1,139 @@
/*
* 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.fabric.impl.renderer;
import java.util.Map;
import java.util.function.Consumer;
import net.fabricmc.fabric.api.renderer.v1.model.SpriteFinder;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.client.texture.MissingSprite;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.util.Identifier;
/**
* Indexes an atlas sprite to allow fast lookup of Sprites from
* baked vertex coordinates. Implementation is a straightforward
* quad tree. Other options that were considered were linear search
* (slow) and direct indexing of fixed-size cells. Direct indexing
* would be fastest but would be memory-intensive for large atlases
* and unsuitable for any atlas that isn't consistently aligned to
* 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);
}
@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

@ -0,0 +1,51 @@
/*
* 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.fabric.mixin.renderer.client;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.world.ExtendedBlockView;
import org.spongepowered.asm.mixin.Mixin;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
/**
* Avoids instanceof checks and enables consistent code path for all baked models.
*/
@Mixin(BakedModel.class)
public interface MixinBakedModel extends FabricBakedModel {
@Override
public default boolean isVanillaAdapter() {
return true;
}
@Override
public default void emitBlockQuads(ExtendedBlockView 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

@ -0,0 +1,74 @@
/*
* 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.fabric.mixin.renderer.client;
import java.util.Random;
import net.fabricmc.fabric.impl.renderer.DamageModel;
import org.apache.commons.lang3.tuple.MutablePair;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Implements hook for block-breaking render.
*/
@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(BlockState blockState, BlockPos blockPos, Sprite sprite, ExtendedBlockView 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, Tessellator.getInstance().getBufferBuilder(), true, this.random, blockState.getRenderingSeed(blockPos));
ci.cancel();
}
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.fabric.mixin.renderer.client;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.minecraft.client.gui.hud.DebugHud;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(DebugHud.class)
public class MixinDebugHud {
@Inject(at = @At("RETURN"), method = "getLeftText")
protected void getLeftText(CallbackInfoReturnable<List<String>> info) {
if (RendererAccess.INSTANCE.hasRenderer()) {
info.getReturnValue().add("[Fabric] Active renderer: " + RendererAccess.INSTANCE.getRenderer().getClass().getSimpleName());
} else {
info.getReturnValue().add("[Fabric] Active renderer: none (vanilla)");
}
}
}

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.fabric.mixin.renderer.client;
import java.util.Map;
import net.fabricmc.fabric.impl.renderer.SpriteFinderImpl;
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 net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
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;
}
@Override
public SpriteFinderImpl fabric_spriteFinder() {
SpriteFinderImpl result = fabric_spriteFinder;
if(result == null) {
result = new SpriteFinderImpl(sprites);
fabric_spriteFinder = result;
}
return result;
}
}

View file

@ -0,0 +1,11 @@
{
"required": false,
"package": "net.fabricmc.fabric.mixin.renderer",
"compatibilityLevel": "JAVA_8",
"client": [
"client.MixinDebugHud"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,13 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.renderer",
"compatibilityLevel": "JAVA_8",
"client": [
"client.MixinBakedModel",
"client.MixinBlockRenderManager",
"client.MixinSpriteAtlasTexture"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,14 @@
{
"schemaVersion": 1,
"id": "fabric-renderer-api-v1",
"version": "${version}",
"license": "Apache-2.0",
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"mixins": [
"fabric-renderer-api-v1.mixins.json",
"fabric-renderer-api-v1.debughud.mixins.json"
]
}

View file

@ -0,0 +1,7 @@
archivesBaseName = "fabric-renderer-indigo"
version = getSubprojectVersion(project, "0.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
compile project(path: ':fabric-renderer-api-v1', configuration: 'dev')
}

View file

@ -0,0 +1,37 @@
/*
* 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;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Indigo implements ClientModInitializer {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public void onInitializeClient() {
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
LOGGER.info("Loading Indigo renderer!");
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
} else {
LOGGER.info("Different rendering plugin detected; not applying Indigo.");
}
}
}

View file

@ -0,0 +1,80 @@
/*
* 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;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public class IndigoMixinConfigPlugin implements IMixinConfigPlugin {
private static final String JSON_ELEMENT = "fabric-renderer-api-v1:contains_renderer";
private static Boolean indigoApplicable;
static boolean shouldApplyIndigo() {
if (indigoApplicable != null) return indigoApplicable;
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
if (container.getMetadata().containsCustomElement(JSON_ELEMENT)) {
indigoApplicable = false;
return false;
}
}
indigoApplicable = true;
return true;
}
@Override
public void onLoad(String mixinPackage) {
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return shouldApplyIndigo();
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}

View file

@ -0,0 +1,69 @@
/*
* 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;
import java.util.HashMap;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.renderer.mesh.MeshBuilderImpl;
import net.minecraft.util.Identifier;
/**
* The Fabric default renderer implementation. Supports all
* 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() { };
@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

@ -0,0 +1,179 @@
/*
* 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;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.block.BlockRenderLayer;
/**
* Default implementation of the standard render materials.
* The underlying representation is simply an int with bit-wise
* packing of the various material properties. This offers
* easy/fast interning via int/object hashmap.
*/
public abstract class RenderMaterialImpl {
private static final BlockRenderLayer[] BLEND_MODES = BlockRenderLayer.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 = 3;
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 BlockRenderLayer 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;
}
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;
}
@Override
public MaterialFinder clear() {
bits = 0;
return this;
}
@Override
public MaterialFinder blendMode(int textureIndex, BlockRenderLayer blendMode) {
final int shift = BLEND_MODE_SHIFT[textureIndex];
bits = (bits & ~(BLEND_MODE_MASK << shift)) | (blendMode.ordinal() << shift);
return this;
}
@Override
public MaterialFinder disableColorIndex(int textureIndex, boolean disable) {
final int flag = COLOR_DISABLE_FLAGS[textureIndex];
bits = disable ? (bits | flag) : (bits & ~flag);
return this;
}
@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;
}
@Override
public MaterialFinder emissive(int textureIndex, boolean isEmissive) {
final int flag = EMISSIVE_FLAGS[textureIndex];
bits = isEmissive ? (bits | flag) : (bits & ~flag);
return this;
}
@Override
public MaterialFinder disableDiffuse(int textureIndex, boolean disable) {
final int flag = DIFFUSE_FLAGS[textureIndex];
bits = disable ? (bits | flag) : (bits & ~flag);
return this;
}
@Override
public MaterialFinder disableAo(int textureIndex, boolean disable) {
final int flag = AO_FLAGS[textureIndex];
bits = disable ? (bits | flag) : (bits & ~flag);
return this;
}
}
}

View file

@ -0,0 +1,24 @@
/*
* 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;
public interface AccessBufferBuilder {
void fabric_putVanillaData(int[] data, int start);
double fabric_offsetX();
double fabric_offsetY();
double fabric_offsetZ();
}

View file

@ -0,0 +1,24 @@
/*
* 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 net.minecraft.client.render.BufferBuilder;
import net.minecraft.util.math.BlockPos;
public interface AccessChunkRenderer {
void fabric_beginBufferBuilding(BufferBuilder bufferBuilder_1, BlockPos blockPos_1);
}

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 net.fabricmc.indigo.renderer.render.TerrainRenderContext;
/**
* Used to stash block renderer reference in local scope during
* chunk rebuild, thus avoiding repeated thread-local lookups.
*/
public interface AccessChunkRendererRegion {
TerrainRenderContext fabric_getRenderer();
void fabric_setRenderer(TerrainRenderContext renderer);
}

View file

@ -0,0 +1,506 @@
/*
* 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 static java.lang.Math.max;
import static net.fabricmc.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG;
import static net.fabricmc.indigo.renderer.helper.GeometryHelper.CUBIC_FLAG;
import static net.fabricmc.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG;
import static net.minecraft.util.math.Direction.DOWN;
import static net.minecraft.util.math.Direction.EAST;
import static net.minecraft.util.math.Direction.NORTH;
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.function.ToIntBiFunction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
import net.fabricmc.indigo.renderer.aocalc.AoFace.WeightFunction;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.ExtendedBlockView;
/**
* Adaptation of inner, non-static class in BlockModelRenderer that serves same purpose.
*/
@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();
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 ToIntBiFunction<BlockState, 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, ToIntBiFunction<BlockState, 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;
}
/** Set true in dev env to confirm results match vanilla when they should */
private static final boolean DEBUG = Boolean.valueOf(System.getProperty("fabric.debugAoLighting", "false"));
// TODO: make actually configurable
private static boolean fixSmoothLighting = true;
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
// TODO: make this actually configurable
final AoConfig config = AoConfig.ENHANCED;
boolean shouldMatch = false;
switch(config) {
case VANILLA:
calcVanilla(quad);
break;
case EMULATE:
calcFastVanilla(quad);
shouldMatch = DEBUG && isVanilla;
break;
case HYBRID:
if(isVanilla) {
calcFastVanilla(quad);
break;
}
// else fall through to enhanced
default:
case ENHANCED:
shouldMatch = calcEnhanced(quad);
}
if (shouldMatch) {
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.equalsApproximate(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) {
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) && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
flags |= LIGHT_FACE_FLAG;
}
switch(flags) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
vanillaFullFace(quad, true);
break;
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
break;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
vanillaFullFace(quad, false);
break;
default:
case AXIS_ALIGNED_FLAG:
vanillaPartialFace(quad, false);
break;
}
}
/** returns true if should match vanilla results */
private boolean calcEnhanced(MutableQuadViewImpl quad) {
switch(quad.geometryFlags()) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
vanillaFullFace(quad, true);
return DEBUG;
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
return DEBUG;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
blendedFullFace(quad);
return false;
case AXIS_ALIGNED_FLAG:
blendedPartialFace(quad);
return false;
default:
irregularFace(quad);
return false;
}
}
private void vanillaFullFace(MutableQuadViewImpl quad, boolean isOnLightFace) {
final Direction lightFace = quad.lightFace();
computeFace(lightFace, isOnLightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
}
private void vanillaPartialFace(MutableQuadViewImpl 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(VertexEditorImpl, Direction)} 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(VertexEditorImpl, Direction)} 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.equalsApproximate(w1, 0)) {
return computeFace(lightFace, true);
} else if(MathHelper.equalsApproximate(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 blendedFullFace(MutableQuadViewImpl quad) {
final Direction lightFace = quad.lightFace();
blendedInsetFace(quad, 0, lightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
}
private void blendedPartialFace(MutableQuadViewImpl 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.x();
if(!MathHelper.equalsApproximate(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;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
}
final float y = normal.y();
if(!MathHelper.equalsApproximate(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;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
}
final float z = normal.z();
if(!MathHelper.equalsApproximate(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;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
}
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 ExtendedBlockView world = blockInfo.blockView;
final BlockState blockState = blockInfo.blockState;
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(blockState, searchPos);
final float ao0 = aoFunc.apply(searchPos);
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);
final int light1 = brightnessFunc.applyAsInt(blockState, searchPos);
final float ao1 = aoFunc.apply(searchPos);
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);
final int light2 = brightnessFunc.applyAsInt(blockState, searchPos);
final float ao2 = aoFunc.apply(searchPos);
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);
final int light3 = brightnessFunc.applyAsInt(blockState, 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(!fixSmoothLighting) searchPos.setOffset(lightFace);
final boolean isClear0 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
final boolean isClear1 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
final boolean isClear2 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
final boolean isClear3 = world.getBlockState(searchPos).getLightSubtracted(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(blockState, 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(blockState, 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(blockState, 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(blockState, 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(blockState, searchPos);
} else {
lightCenter = brightnessFunc.applyAsInt(blockState, 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(fixSmoothLighting) {
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

@ -0,0 +1,63 @@
/*
* 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;
/**
* Defines the configuration modes for the AoCalculator.
* 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>
*
* Unit (full face) quads must still have the vanilla fixed winding order but smaller
* 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

@ -0,0 +1,128 @@
/*
* 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 static net.minecraft.util.math.Direction.*;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.util.SystemUtil;
import net.minecraft.util.math.Direction;
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
/**
* Adapted from vanilla BlockModelRenderer.AoCalculator.
*/
@Environment(EnvType.CLIENT)
enum AoFace {
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> q.y(i),
(q, i, w) -> {
final float u = q.x(i);
final float v = 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 - q.y(i),
(q, i, w) -> {
final float u = q.x(i);
final float v = 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) -> q.z(i),
(q, i, w) -> {
final float u = q.y(i);
final float v = 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 - q.z(i),
(q, i, w) -> {
final float u = q.y(i);
final float v = 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) -> q.x(i),
(q, i, w) -> {
final float u = q.y(i);
final float v = 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 - q.x(i),
(q, i, w) -> {
final float u = q.y(i);
final float v = 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;
});
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

@ -0,0 +1,121 @@
/*
* 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;
/**
* Holds per-corner results for a single block face.
* 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 maxBlockLight(int oldMax) {
final int i = b0 > b1 ? b0 : b1;
final int j = b2 > b3 ? b2 : b3;
return Math.max(oldMax, i > j ? i : j);
}
int weigtedSkyLight(float[] w) {
return (int) (s0 * w[0] + s1 * w[1] + s2 * w[2] + s3 * w[3]) & 0xFF;
}
int maxSkyLight(int oldMax) {
final int i = s0 > s1 ? s0 : s1;
final int j = s2 > s3 ? s2 : s3;
return Math.max(oldMax, i > j ? i : j);
}
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];
}
float maxAo(float oldMax) {
final float x = a0 > a1 ? a0 : a1;
final float y = a2 > a3 ? a2 : a3;
final float z = x > y ? x : y;
return oldMax > z ? oldMax : z;
}
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

@ -0,0 +1,371 @@
/*
* 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.ToIntBiFunction;
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.ExtendedBlockView;
/**
* 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 ToIntBiFunction<BlockState, BlockPos> brightnessFunc;
private final AoFunc aoFunc;
public VanillaAoCalc(ToIntBiFunction<BlockState, 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(ExtendedBlockView 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(blockState, mpos);
float float_1 = aoFunc.apply(mpos);
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]);
int int_2 = brightnessFunc.applyAsInt(blockState, mpos);
float float_2 = aoFunc.apply(mpos);
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[2]);
int int_3 = brightnessFunc.applyAsInt(blockState, mpos);
float float_3 = aoFunc.apply(mpos);
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[3]);
int int_4 = brightnessFunc.applyAsInt(blockState, mpos);
float float_4 = aoFunc.apply(mpos);
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[0]).setOffset(side);
boolean boolean_1 = blockView.getBlockState(mpos).getLightSubtracted(blockView, mpos) == 0;
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[1]).setOffset(side);
boolean boolean_2 = blockView.getBlockState(mpos).getLightSubtracted(blockView, mpos) == 0;
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[2]).setOffset(side);
boolean boolean_3 = blockView.getBlockState(mpos).getLightSubtracted(blockView, mpos) == 0;
mpos.set((Vec3i)lightPos).setOffset(neighborData.faces[3]).setOffset(side);
boolean boolean_4 = blockView.getBlockState(mpos).getLightSubtracted(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(blockState, 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(blockState, 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(blockState, 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(blockState, mpos);
}
int int_13 = brightnessFunc.applyAsInt(blockState, blockPos);
mpos.set((Vec3i)blockPos).setOffset(side);
if (bits.get(0) || !blockView.getBlockState(mpos).isFullOpaque(blockView, mpos)) {
int_13 = brightnessFunc.applyAsInt(blockState, 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(ExtendedBlockView 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,181 @@
/*
* 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.helper;
import java.nio.ByteOrder;
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.minecraft.client.render.model.BakedQuadFactory;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* Static routines of general utility for renderer implementations.
* Renderers are not required to use these helpers, but they were
* 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() {}
/** 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);
}
/** 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;
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
/** 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);
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
/**
* Same results as {@link BakedQuadFactory#method_3456()}
*/
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.x(), normal.y(), normal.z());
}
/**
* 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

@ -0,0 +1,239 @@
/*
* 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.helper;
import static net.minecraft.util.math.MathHelper.equalsApproximate;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.math.Direction.AxisDirection;
/**
* Static routines of general utility for renderer implementations.
* Renderers are not required to use these helpers, but they were
* 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() {}
/**
* 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 equalsApproximate(val, quad.posByIndex(1, i))
&& equalsApproximate(val, quad.posByIndex(2, i))
&& equalsApproximate(val, quad.posByIndex(3, i));
}
/**
* True if quad - already known to be parallel to a face - is actually coplanar with it.<p>
*
* Test will be unreliable if not already parallel, use {@link #isQuadParallel(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 int coordinateIndex = lightFace.getAxis().ordinal();
final float expectedValue = lightFace.getDirection() == AxisDirection.POSITIVE ? 1 : 0;
return equalsApproximate(quad.posByIndex(0, coordinateIndex), expectedValue);
}
/**
* 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;
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, int[], int, QuadSerializer)}.
* True if quad touches all four corners of unit square.
*/
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(equalsApproximate(a, 0)) {
if(equalsApproximate(b, 0)) {
flags |= 1;
} else if(equalsApproximate(b, 1)) {
flags |= 2;
} else {
return false;
}
} else if(equalsApproximate(a, 1)) {
if(equalsApproximate(b, 0)) {
flags |= 4;
} else if(equalsApproximate(b, 1)) {
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.x() > 0 ? Direction.EAST : Direction.WEST;
case Y:
return normal.y() > 0 ? Direction.UP : Direction.DOWN;
case Z:
return normal.z() > 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.x(), vec.y(), vec.z());
}
/**
* 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

@ -0,0 +1,114 @@
/*
* 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.helper;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3i;
/**
* Static routines of general utility for renderer implementations.
* Renderers are not required to use these helpers, but they were
* designed to be usable without the default renderer.
*/
public abstract class 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.x(), normal.y(), normal.z(), 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

@ -0,0 +1,106 @@
/*
* 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.helper;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
/**
* Handles most texture-baking use cases for model loaders and model libraries
* via {@link #bakeSprite(MutableQuadView, int, Sprite, int)}. Also used by the API
* 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);
}
/**
* 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

@ -0,0 +1,101 @@
/*
* 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.mesh;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
import net.minecraft.util.math.Direction;
/**
* Holds all the array offsets and bit-wise encoders/decoders for
* packing/unpacking quad data in an array of integers.
* 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 = 28;
public static final int NORMALS_OFFSET = VERTEX_START_OFFSET + VANILLA_STRIDE;
static final int NORMALS_STRIDE = 4;
// normals are followed by 0-2 sets of color/uv coordinates
static final int TEXTURE_STRIDE = 12;
/** is one tex stride less than the actual base, because when used tex index is >= 1 */
static final int TEXTURE_OFFSET_MINUS = NORMALS_OFFSET + NORMALS_STRIDE - TEXTURE_STRIDE;
static final int SECOND_TEXTURE_OFFSET = NORMALS_OFFSET + NORMALS_STRIDE;
static final int THIRD_TEXTURE_OFFSET = SECOND_TEXTURE_OFFSET + TEXTURE_STRIDE;
public static final int MAX_STRIDE = HEADER_STRIDE + VANILLA_STRIDE + NORMALS_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);
}
}

View file

@ -0,0 +1,83 @@
/*
* 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.mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.indigo.renderer.helper.ColorHelper;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
/**
* Our implementation of {@link MeshBuilder}, used for static mesh creation and baking.
* Not much to it - mainly it just needs to grow the int[] array as quads are appended
* and maintain/provide a properly-configured {@link MutableQuadView} instance.
* All the encoding and other work is handled in the quad base classes.
* 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;
}
}
@Override
public Mesh build() {
int[] packed = new int[index];
System.arraycopy(data, 0, packed, 0, index);
index = 0;
return new MeshImpl(packed);
}
@Override
public QuadEmitter getEmitter() {
ensureCapacity(EncodingFormat.MAX_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);
geometryFlags = GeometryHelper.computeShapeFlags(this);
ColorHelper.applyDiffuseShading(this, false);
encodeHeader();
index += maker.stride();
ensureCapacity(EncodingFormat.MAX_STRIDE);
baseIndex = index;
clear();
return this;
}
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.mesh;
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;
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 += cursor.stride();
}
}
}

View file

@ -0,0 +1,170 @@
/*
* 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.mesh;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.EMPTY;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.NORMALS_OFFSET;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VANILLA_STRIDE;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_START_OFFSET;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.helper.ColorHelper.ShadeableQuad;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.indigo.renderer.helper.NormalHelper;
import net.fabricmc.indigo.renderer.helper.TextureHelper;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
/**
* Almost-concrete implementation of a mutable quad. The only missing part is {@link #emit()},
* 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 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;
}
@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 nominalFace(Direction face) {
nominalFace = face;
return this;
}
@Override
public final MutableQuadViewImpl colorIndex(int colorIndex) {
this.colorIndex = colorIndex;
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();
if(isItem) {
System.arraycopy(quadData, startIndex, data, vertexStart, 6);
System.arraycopy(quadData, startIndex + 7, data, vertexStart + 7, 6);
System.arraycopy(quadData, startIndex + 14, data, vertexStart + 14, 6);
System.arraycopy(quadData, startIndex + 21, data, vertexStart + 21, 6);
final int normalsIndex = baseIndex + NORMALS_OFFSET;
data[normalsIndex] = quadData[startIndex + 6];
data[normalsIndex + 1] = quadData[startIndex + 13];
data[normalsIndex + 2] = quadData[startIndex + 20];
data[normalsIndex + 3] = quadData[startIndex + 27];
} else {
System.arraycopy(quadData, startIndex, data, vertexStart, 28);
}
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 * 7;
data[index] = Float.floatToRawIntBits(x);
data[index + 1] = Float.floatToRawIntBits(y);
data[index + 2] = Float.floatToRawIntBits(z);
isFaceNormalInvalid = true;
return this;
}
@Override
public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
normalFlags |= (1 << vertexIndex);
data[baseIndex + VERTEX_START_OFFSET + VANILLA_STRIDE + vertexIndex] = NormalHelper.packNormal(x, y, z, 0);
return this;
}
@Override
public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) {
data[baseIndex + vertexIndex * 7 + 6 + VERTEX_START_OFFSET] = lightmap;
return this;
}
@Override
public MutableQuadViewImpl spriteColor(int vertexIndex, int textureIndex, int color) {
data[baseIndex + colorIndex(vertexIndex, textureIndex)] = color;
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;
}
}

View file

@ -0,0 +1,360 @@
/*
* 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.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_TAG;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.NORMALS_OFFSET;
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.VANILLA_STRIDE;
import static net.fabricmc.indigo.renderer.mesh.EncodingFormat.VERTEX_START_OFFSET;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.indigo.renderer.helper.NormalHelper;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
/**
* Base class for all quads / quad makers. Handles the ugly bits
* 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, 28);
if(textureIndex > 0) {
copyColorUV(textureIndex, target, targetIndex);
}
if(isItem) {
copyNormals(target, targetIndex);
}
}
/**
* 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 = 7;
} 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 += 7;
indexFrom += strideFrom;
}
}
/**
* Internal helper method. Copies packed normals to target, assuming vanilla format.
*/
public final void copyNormals(int[] target, int targetIndex) {
final int normalFlags = this.normalFlags;
final int packedFaceNormal = normalFlags == 0b1111 ? 0 : NormalHelper.packNormal(faceNormal(), 0);
final int normalsIndex = baseIndex + NORMALS_OFFSET;
for(int v = 0; v < 4; v++) {
final int packed = (normalFlags & (1 << v)) == 0 ? packedFaceNormal : data[normalsIndex + v];
target[targetIndex + v * 7 + 6] = packed;
}
}
@Override
public final RenderMaterialImpl.Value material() {
return material;
}
@Override
public final int colorIndex() {
return colorIndex;
}
@Override
public final int tag() {
return tag;
}
@Override
public final Direction lightFace() {
return lightFace;
}
@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;
}
@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.x(), this.faceNormal.y(), this.faceNormal.z());
}
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 * 7;
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[vertexStart() + vertexIndex * 7 + coordinateIndex]);
}
@Override
public float x(int vertexIndex) {
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 7]);
}
@Override
public float y(int vertexIndex) {
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 7 + 1]);
}
@Override
public float z(int vertexIndex) {
return Float.intBitsToFloat(data[vertexStart() + vertexIndex * 7 + 2]);
}
@Override
public boolean hasNormal(int vertexIndex) {
return (normalFlags & (1 << vertexIndex)) != 0;
}
@Override
public Vector3f copyNormal(int vertexIndex, Vector3f target) {
if(hasNormal(vertexIndex)) {
if(target == null) {
target = new Vector3f();
}
final int normal = data[vertexStart() + VANILLA_STRIDE + 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[baseIndex + VERTEX_START_OFFSET + VANILLA_STRIDE + vertexIndex], 0)
: Float.NaN;
}
@Override
public float normalY(int vertexIndex) {
return hasNormal(vertexIndex)
? NormalHelper.getPackedNormalComponent(data[baseIndex + VERTEX_START_OFFSET + VANILLA_STRIDE + vertexIndex], 1)
: Float.NaN;
}
@Override
public float normalZ(int vertexIndex) {
return hasNormal(vertexIndex)
? NormalHelper.getPackedNormalComponent(data[baseIndex + VERTEX_START_OFFSET + VANILLA_STRIDE + vertexIndex], 2)
: Float.NaN;
}
@Override
public int lightmap(int vertexIndex) {
return data[baseIndex + vertexIndex * 7 + 6 + VERTEX_START_OFFSET];
}
protected int colorIndex(int vertexIndex, int textureIndex) {
return textureIndex == 0 ? vertexIndex * 7 + 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 float spriteU(int vertexIndex, int textureIndex) {
return Float.intBitsToFloat(data[baseIndex + colorIndex(vertexIndex, textureIndex) + 1]);
}
@Override
public float spriteV(int vertexIndex, int textureIndex) {
return Float.intBitsToFloat(data[baseIndex + colorIndex(vertexIndex, textureIndex) + 2]);
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.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.CallbackInfoReturnable;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.indigo.renderer.render.BlockRenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.color.block.BlockColors;
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.world.ExtendedBlockView;
@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(ExtendedBlockView 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));
}
}
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.nio.IntBuffer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.minecraft.client.render.BufferBuilder;
@Mixin(BufferBuilder.class)
public abstract class MixinBufferBuilder implements AccessBufferBuilder {
@Shadow private IntBuffer bufInt;
@Shadow private int vertexCount;
@Shadow private double offsetX;
@Shadow private double offsetY;
@Shadow private double offsetZ;
@Shadow abstract void grow(int size);
@Shadow abstract int getCurrentSize();
private static final int QUAD_STRIDE_INTS = 28;
private static final int QUAD_STRIDE_BYTES = QUAD_STRIDE_INTS * 4;
/**
* Similar to {@link BufferBuilder#putVertexData(int[])} but
* accepts an array index so that arrays containing more than one
* quad don't have to be copied to a transfer array before the call.
*/
@Override
public void fabric_putVanillaData(int[] data, int start) {
this.grow(QUAD_STRIDE_BYTES);
this.bufInt.position(this.getCurrentSize());
this.bufInt.put(data, start, QUAD_STRIDE_INTS);
this.vertexCount += 4;
}
@Override
public double fabric_offsetX() {
return offsetX;
}
@Override
public double fabric_offsetY() {
return offsetY;
}
@Override
public double fabric_offsetZ() {
return offsetZ;
}
}

View file

@ -0,0 +1,50 @@
/*
* 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 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.CallbackInfoReturnable;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRendererRegion;
import net.fabricmc.indigo.renderer.render.TerrainRenderContext;
import net.minecraft.client.render.chunk.ChunkRenderTask;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
@Mixin(ChunkRenderTask.class)
public abstract class MixinChunkRenderTask {
@Shadow private ChunkRendererRegion region;
/**
* The block view reference is voided when {@link ChunkRenderTask#getAndInvalidateWorldView()} is called during
* chunk rebuild, but we need it and it is harder to make reliable, non-invasive changes there.
* So we capture the block view before the reference is voided and send it to the renderer. <p>
*
* We also store a reference to the renderer in the view to avoid doing thread-local lookups for each block.
*/
@Inject(at = @At("HEAD"), method = "takeRegion")
private void chunkDataHook(CallbackInfoReturnable<ChunkRendererRegion> info) {
final ChunkRendererRegion blockView = region;
if(blockView != null) {
final TerrainRenderContext renderer = TerrainRenderContext.POOL.get();
renderer.setBlockView(blockView);
((AccessChunkRendererRegion)blockView).fabric_setRenderer(renderer);
}
}
}

View file

@ -0,0 +1,130 @@
/*
* 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.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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRenderer;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRendererRegion;
import net.fabricmc.indigo.renderer.render.TerrainRenderContext;
import net.minecraft.block.BlockRenderLayer;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.chunk.ChunkRenderData;
import net.minecraft.client.render.chunk.ChunkRenderTask;
import net.minecraft.client.render.chunk.ChunkRenderer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Implements the main hooks for terrain rendering. Attempts to tread
* lightly. This means we are deliberately stepping over some minor
* optimization opportunities.<p>
*
* Non-Fabric renderer implementations that are looking to maximize
* performance will likely take a much more aggressive approach.
* For that reason, mod authors who want compatibility with advanced
* renderers will do well to steer clear of chunk rebuild hooks unless
* they are creating a renderer.<p>
*
* These hooks are intended only for the Fabric default renderer and
* aren't expected to be present when a different renderer is being used.
* Renderer authors are responsible for creating the hooks they need.
* (Though they can use these as a example if they wish.)
*/
@Mixin(ChunkRenderer.class)
public abstract class MixinChunkRenderer implements AccessChunkRenderer{
@Shadow private BlockPos.Mutable origin;
@Shadow abstract void beginBufferBuilding(BufferBuilder bufferBuilder_1, BlockPos blockPos_1);
@Shadow abstract void endBufferBuilding(BlockRenderLayer blockRenderLayer_1, float float_1, float float_2, float float_3, BufferBuilder bufferBuilder_1, ChunkRenderData chunkRenderData_1);
/**
* Access method for renderer.
*/
@Override
public void fabric_beginBufferBuilding(BufferBuilder bufferBuilder_1, BlockPos blockPos_1) {
beginBufferBuilding(bufferBuilder_1, blockPos_1);
}
/**
* Save task to renderer, this is the easiest place to capture it.
*/
@Inject(at = @At("HEAD"), method = "rebuildChunk")
private void hookRebuildChunkHead(float float_1, float float_2, float float_3, ChunkRenderTask chunkRenderTask_1, CallbackInfo info) {
if(chunkRenderTask_1 != null) {
TerrainRenderContext renderer = TerrainRenderContext.POOL.get();
renderer.setChunkTask(chunkRenderTask_1);
}
}
/**
* Capture the block layer result flags when they are first created so our renderer
* can update then when more than one layer is renderer for a single model.
* This is also where we signal the renderer to prepare for a new chunk using
* the data we've accumulated up to this point.
*/
@ModifyVariable(method = "rebuildChunk", at = @At(value = "STORE", ordinal = 0), allow = 1, require = 1)
private boolean[] hookResultFlagsAndPrepare(boolean[] flagsIn) {
TerrainRenderContext.POOL.get().prepare((ChunkRenderer)(Object)this, origin, flagsIn);
return flagsIn;
}
/**
* This is the hook that actually implements the rendering API for terrain rendering.<p>
*
* It's unusual to have a @Redirect in a Fabric library, but in this case
* it is our explicit intention that {@link BlockRenderManager#tesselateBlock(BlockState, BlockPos, ExtendedBlockView, BufferBuilder, Random)}
* does not execute for models that will be rendered by our renderer.<p>
*
* Any mod that wants to redirect this specific call is likely also a renderer, in which case this
* renderer should not be present, or the mod should probably instead be relying on the renderer API
* which was specifically created to provide for enhanced terrain rendering.<p>
*
* Note also that {@link BlockRenderManager#tesselateBlock(BlockState, BlockPos, ExtendedBlockView, BufferBuilder, Random)}
* IS called if the block render type is something other than {@link BlockRenderType#MODEL}.
* 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 = "rebuildChunk", 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/ExtendedBlockView;Lnet/minecraft/client/render/BufferBuilder;Ljava/util/Random;)Z"))
private boolean hookChunkBuildTesselate(BlockRenderManager renderManager, BlockState blockState, BlockPos blockPos, ExtendedBlockView blockView, BufferBuilder bufferBuilder, Random random) {
if(blockState.getRenderType() == BlockRenderType.MODEL) {
return ((AccessChunkRendererRegion)blockView).fabric_getRenderer().tesselateBlock(blockState, blockPos);
} else {
return renderManager.tesselateBlock(blockState, blockPos, blockView, bufferBuilder, random);
}
}
/**
* Release all references. Probably not necessary but would be $#%! to debug if it is.
*/
@Inject(at = @At("RETURN"), method = "rebuildChunk")
private void hookRebuildChunkReturn(float float_1, float float_2, float float_3, ChunkRenderTask chunkRenderTask_1, CallbackInfo info) {
TerrainRenderContext.POOL.get().release();
}
}

View file

@ -0,0 +1,38 @@
/*
* 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 org.spongepowered.asm.mixin.Mixin;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRendererRegion;
import net.fabricmc.indigo.renderer.render.TerrainRenderContext;
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;
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.List;
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 net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.indigo.renderer.render.ItemRenderContext;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
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();
}
}
}

View file

@ -0,0 +1,152 @@
/*
* 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.render;
import java.util.function.Consumer;
import java.util.function.ToIntBiFunction;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.BlockPos;
/**
* Consumer for pre-baked meshes. Works by copying the mesh data to a
* "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, ToIntBiFunction<BlockState, BlockPos> brightnessFunc, Int2ObjectFunction<AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
super(blockInfo, brightnessFunc, 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() {
if(blockInfo.shouldDrawFace(this.cullFace())) {
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;
}
final RenderMaterialImpl.Value mat = q.material();
final int textureCount = mat.spriteDepth();
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);
}
}
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 int renderLayer = blockInfo.layerIndexOrDefault(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);
}
}
}
}

View file

@ -0,0 +1,148 @@
/*
* 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.render;
import static net.fabricmc.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG;
import java.util.function.ToIntBiFunction;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
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.BlockState;
import net.minecraft.util.math.BlockPos;
/**
* Base quad-rendering class for fallback and mesh consumers.
* 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 ToIntBiFunction<BlockState, BlockPos> brightnessFunc;
protected final Int2ObjectFunction<AccessBufferBuilder> bufferFunc;
protected final BlockRenderInfo blockInfo;
protected final AoCalculator aoCalc;
protected final QuadTransform transform;
AbstractQuadRenderer(BlockRenderInfo blockInfo, ToIntBiFunction<BlockState, BlockPos> brightnessFunc, Int2ObjectFunction<AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
this.blockInfo = blockInfo;
this.brightnessFunc = brightnessFunc;
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, int renderLayer) {
bufferFunc.get(renderLayer).fabric_putVanillaData(quad.data(), quad.vertexStart());
}
// 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, int 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, int 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, int 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, int 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 + 7];
lightmaps[2] = data[EncodingFormat.VERTEX_START_OFFSET + 6 + 14];
lightmaps[3] = data[EncodingFormat.VERTEX_START_OFFSET + 6 + 21];
}
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 + 7] = lightmaps[1];
data[EncodingFormat.VERTEX_START_OFFSET + 6 + 14] = lightmaps[2];
data[EncodingFormat.VERTEX_START_OFFSET + 6 + 21] = 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) {
mpos.setOffset(quad.lightFace());
}
return brightnessFunc.applyAsInt(blockState, mpos);
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.render;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
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);
}
@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

@ -0,0 +1,142 @@
/*
* 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.render;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.ToIntBiFunction;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.BlockState;
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.world.ExtendedBlockView;
/**
* 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::brightness, 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(BlockState blockState, BlockPos pos) {
if(blockInfo.blockView == null) {
return 15 << 20 | 15 << 4;
}
return blockState.getBlockBrightness(blockInfo.blockView, pos);
}
private float aoLevel(BlockPos pos) {
final ExtendedBlockView blockView = blockInfo.blockView;
if(blockView == null) {
return 1f;
}
return blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
}
private AccessBufferBuilder outputBuffer(int renderLayer) {
didOutput = true;
return fabricBuffer;
}
public boolean tesselate(BlockModelRenderer vanillaRenderer, ExtendedBlockView 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 AccessBufferBuilder buffer = fabricBuffer;
final BlockPos pos = blockInfo.blockPos;
offsetX = buffer.fabric_offsetX() + pos.getX();
offsetY = buffer.fabric_offsetY() + pos.getY();
offsetZ = buffer.fabric_offsetZ() + pos.getZ();
}
private class MeshConsumer extends AbstractMeshConsumer {
MeshConsumer(BlockRenderInfo blockInfo, ToIntBiFunction<BlockState, BlockPos> brightnessFunc, Int2ObjectFunction<AccessBufferBuilder> bufferFunc, AoCalculator aoCalc, QuadTransform transform) {
super(blockInfo, brightnessFunc, 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

@ -0,0 +1,87 @@
/*
* 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.render;
import java.util.Random;
import java.util.function.Supplier;
import net.minecraft.block.BlockRenderLayer;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.ExtendedBlockView;
/**
* Holds, manages and provides access to the block/world related state
* needed by fallback and mesh consumers.<p>
*
* Exception: per-block position offsets are tracked in {@link ChunkRenderInfo}
* 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 ExtendedBlockView blockView;
public BlockPos blockPos;
public BlockState blockState;
public long seed;
boolean defaultAo;
int defaultLayerIndex;
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(ExtendedBlockView 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;
defaultLayerIndex = blockState.getBlock().getRenderLayer().ordinal();
}
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;
}
int layerIndexOrDefault(BlockRenderLayer layer) {
return layer == null ? this.defaultLayerIndex : layer.ordinal();
}
}

View file

@ -0,0 +1,196 @@
/*
* 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.render;
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRenderer;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.Block.OffsetType;
import net.minecraft.block.BlockRenderLayer;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.chunk.ChunkRenderData;
import net.minecraft.client.render.chunk.ChunkRenderTask;
import net.minecraft.client.render.chunk.ChunkRenderer;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.ExtendedBlockView;
/**
* Holds, manages and provides access to the chunk-related state
* needed by fallback and mesh consumers during terrain rendering.<p>
*
* Exception: per-block position offsets are tracked here so they can
* 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;
ChunkRenderTask chunkTask;
ChunkRenderData chunkData;
ChunkRenderer chunkRenderer;
ExtendedBlockView blockView;
boolean [] resultFlags;
private final AccessBufferBuilder[] buffers = new AccessBufferBuilder[4];
private final BlockRenderLayer[] LAYERS = BlockRenderLayer.values();
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 setBlockView(ChunkRendererRegion blockView) {
this.blockView = blockView;
}
void setChunkTask(ChunkRenderTask chunkTask) {
this.chunkTask = chunkTask;
}
void prepare(ChunkRenderer chunkRenderer, BlockPos.Mutable chunkOrigin, boolean [] resultFlags) {
this.chunkData = chunkTask.getRenderData();
this.chunkRenderer = chunkRenderer;
this.resultFlags = resultFlags;
buffers[0] = null;
buffers[1] = null;
buffers[2] = null;
buffers[3] = null;
chunkOffsetX = -chunkOrigin.getX();
chunkOffsetY = -chunkOrigin.getY();
chunkOffsetZ = -chunkOrigin.getZ();
brightnessCache.clear();
aoLevelCache.clear();
}
void release() {
chunkData = null;
chunkTask = null;
chunkRenderer = null;
buffers[0] = null;
buffers[1] = null;
buffers[2] = null;
buffers[3] = null;
}
void beginBlock() {
final BlockState blockState = blockInfo.blockState;
final BlockPos blockPos = blockInfo.blockPos;
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(int layerIndex) {
// redundant for first layer, but probably not faster to check
resultFlags[layerIndex] = true;
AccessBufferBuilder result = buffers[layerIndex];
if (result == null) {
BufferBuilder builder = chunkTask.getBufferBuilders().get(layerIndex);
buffers[layerIndex] = (AccessBufferBuilder) builder;
BlockRenderLayer layer = LAYERS[layerIndex];
if (!chunkData.isBufferInitialized(layer)) {
chunkData.markBufferInitialized(layer); // start buffer
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, blockInfo.blockPos);
}
result = (AccessBufferBuilder) builder;
}
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(ExtendedBlockView, BlockPos)}.
* See also the comments for {@link #brightnessCache}.
*/
int cachedBrightness(BlockState blockState, BlockPos pos) {
long key = pos.asLong();
int result = brightnessCache.get(key);
if (result == Integer.MAX_VALUE) {
result = blockState.getBlockBrightness(blockView, 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 = blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
aoLevelCache.put(key, result);
}
return result;
}
}

View file

@ -0,0 +1,241 @@
/*
* 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.render;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.lwjgl.opengl.GL11;
import com.mojang.blaze3d.platform.GlStateManager;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.helper.ColorHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.BlockState;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.item.ItemStack;
/**
* The render context used for item rendering.
* Does not implement emissive lighting for sake
* 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);
}
private final ItemColors colorMap;
private final Random random = new Random();
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;
}
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) {
GlStateManager.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() {
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;
GlStateManager.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_putVanillaData(quadData, EncodingFormat.VERTEX_START_OFFSET);
}
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();
// A bit of a hack - copy packed normals on top of lightmaps.
// Violates normal encoding format but the editor content will be discarded
// and this avoids the step of copying to a separate array.
quad.copyNormals(quadData, EncodingFormat.VERTEX_START_OFFSET);
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 Consumer<BakedModel> fallbackConsumer = model -> {
for(int i = 0; i < 7; i++) {
random.setSeed(42L);
vanillaHandler.accept(bufferBuilder, model.getQuads((BlockState)null, ModelHelper.faceFromIndex(i), random), color, itemStack);
}
};
@Override
public Consumer<BakedModel> fallbackConsumer() {
return fallbackConsumer;
}
@Override
public QuadEmitter getEmitter() {
editorQuad.clear();
return editorQuad;
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.render;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
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;
}
}
}

View file

@ -0,0 +1,144 @@
/*
* 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.render;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.util.math.Direction;
/**
* Consumer for vanilla baked models. Generally intended to give visual results matching a vanilla render,
* however there could be subtle (and desirable) lighting variations so is good to be able to render
* everything consistently.<p>
*
* Also, the API allows multi-part models that hold multiple vanilla models to render them without
* combining quad lists, but the vanilla logic only handles one model per block. To route all of
* them through vanilla logic would require additional hooks.<p>
*
* Works by copying the quad data to an "editor" quad held in the instance,
* where all transformations are applied before buffering. Transformations should be
* the same as they would be in a vanilla render - the editor is serving mainly
* as a way to access vertex data without magical numbers. It also allows a consistent interface
* for downstream tesselation routines.<p>
*
* Another difference from vanilla render is that all transformation happens before the
* vertex data is sent to the byte buffer. Generally POJO array access will be faster than
* 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[28];
private final ChunkRenderInfo chunkInfo;
TerrainFallbackConsumer(BlockRenderInfo blockInfo, ChunkRenderInfo chunkInfo, AoCalculator aoCalc, QuadTransform transform) {
super(blockInfo, chunkInfo::cachedBrightness, chunkInfo::getInitializedBuffer, aoCalc, transform);
this.chunkInfo = chunkInfo;
}
private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
{
data = editorBuffer;
material = MATERIAL_SHADED;
baseIndex = -EncodingFormat.HEADER_STRIDE;
}
@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) {
System.arraycopy(quad.getVertexData(), 0, editorBuffer, 0, 28);
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.defaultLayerIndex, editorQuad.colorIndex());
} else {
// vanilla compatibility hack
// For flat lighting, if cull face is set always use neighbor light.
// Otherwise still need to ensure geometry is updated before offsets are applied
if(cullFace == null) {
editorQuad.invalidateShape();
editorQuad.geometryFlags();
} else {
editorQuad.geometryFlags(GeometryHelper.AXIS_ALIGNED_FLAG | GeometryHelper.LIGHT_FACE_FLAG);
}
chunkInfo.applyOffsets(editorQuad);
tesselateFlat(editorQuad, blockInfo.defaultLayerIndex, editorQuad.colorIndex());
}
}
}

View file

@ -0,0 +1,34 @@
/*
* 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.render;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
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::cachedBrightness, chunkInfo::getInitializedBuffer, aoCalc, transform);
this.chunkInfo = chunkInfo;
}
@Override
protected void applyOffsets(MutableQuadViewImpl quad) {
chunkInfo.applyOffsets(quad);
}
}

View file

@ -0,0 +1,103 @@
/*
* 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.render;
import java.util.function.Consumer;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.chunk.ChunkRenderTask;
import net.minecraft.client.render.chunk.ChunkRenderer;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.util.crash.CrashException;
import net.minecraft.util.crash.CrashReport;
import net.minecraft.util.crash.CrashReportSection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Implementation of {@link RenderContext} used during terrain rendering.
* Dispatches calls from models during chunk rebuild to the appropriate consumer,
* 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);
private final BlockRenderManager blockRenderManager = MinecraftClient.getInstance().getBlockRenderManager();
public void setBlockView(ChunkRendererRegion blockView) {
blockInfo.setBlockView(blockView);
chunkInfo.setBlockView(blockView);
}
public void setChunkTask(ChunkRenderTask chunkTask) {
chunkInfo.setChunkTask(chunkTask);
}
public TerrainRenderContext prepare(ChunkRenderer chunkRenderer, BlockPos.Mutable chunkOrigin, boolean [] resultFlags) {
chunkInfo.prepare(chunkRenderer, chunkOrigin, resultFlags);
return this;
}
public void release() {
chunkInfo.release();
blockInfo.release();
}
/** Called from chunk renderer hook. */
public boolean tesselateBlock(BlockState blockState, BlockPos blockPos) {
try {
final BakedModel model = blockRenderManager.getModel(blockState);
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);
}
return chunkInfo.resultFlags[blockInfo.defaultLayerIndex];
}
@Override
public Consumer<Mesh> meshConsumer() {
return meshConsumer;
}
@Override
public Consumer<BakedModel> fallbackConsumer() {
return fallbackConsumer;
}
@Override
public QuadEmitter getEmitter() {
return meshConsumer.getEmitter();
}
}

View file

@ -0,0 +1,17 @@
{
"required": true,
"package": "net.fabricmc.indigo.renderer.mixin",
"compatibilityLevel": "JAVA_8",
"plugin": "net.fabricmc.indigo.IndigoMixinConfigPlugin",
"mixins": [
"MixinBlockModelRenderer",
"MixinBufferBuilder",
"MixinChunkRenderer",
"MixinChunkRendererRegion",
"MixinChunkRenderTask",
"MixinItemRenderer"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,19 @@
{
"schemaVersion": 1,
"id": "fabric-renderer-indigo",
"version": "${version}",
"license": "Apache-2.0",
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*",
"fabric-renderer-api-v1": "*"
},
"mixins": [
"fabric-renderer-indigo.mixins.json"
],
"entrypoints": {
"client": [
"net.fabricmc.indigo.Indigo"
]
}
}

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-rendering-data-attachment-v1"
version = getSubprojectVersion(project, "0.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

View file

@ -0,0 +1,64 @@
/*
* 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.fabric.api.rendering.data.v1;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* BlockView-extending interface to be used by {@link FabricBakedModel} for dynamic model
* customization. It ensures thread safety and exploits data cached in render
* chunks for performance and data consistency.<p>
*
* There are differences from BlockView consumers must understand:<p>
*
* BlockEntity implementations that provide data for model customization should implement
* {@link RenderAttachmentBlockEntity} which will be queried on the main thread when a render
* chunk is enqueued for rebuild. The model should retrieve the results via {@link #getCachedRenderData()}.
* While {@link #getBlockEntity(net.minecraft.util.math.BlockPos)} is not disabled, it
* is not thread-safe for use on render threads. Models that violate this guidance are
* responsible for any necessary synchronization or collision detection.<p>
*
* {@link #getBlockState(net.minecraft.util.math.BlockPos)} and {@link #getFluidState(net.minecraft.util.math.BlockPos)}
* will always reflect the state cached with the render chunk. Block and fluid states
* can thus be different from main-thread world state due to lag between block update
* application from network packets and render chunk rebuilds. Use of {@link #getCachedRenderData()}
* will ensure consistency of model state with the rest of the chunk being rendered.<p>
*
* Models should avoid using {@link ExtendedBlockView#getBlockEntity(BlockPos)}
* to ensure thread safety because this view may be accessed outside the main client thread.
* Models that require Block Entity data should implement {@link RenderAttachmentBlockEntity}
* and then use {@link #getBlockEntityRenderAttachment(BlockPos)} to retrieve it. When called from the
* main thread, that method will simply retrieve the data directly.<p>
*
* This interface is only guaranteed to be present in the client environment.
*/
public interface RenderAttachedBlockView extends ExtendedBlockView {
/**
* For models associated with Block Entities that implement {@link RenderAttachmentBlockEntity}
* this will be the most recent value provided by that implementation for the given block position.<p>
*
* Null in all other cases, or if the result from the implementation was null.<p>
*
* @param pos Position of the block for the block model.
*/
default Object getBlockEntityRenderAttachment(BlockPos pos) {
BlockEntity be = this.getBlockEntity(pos);
return be == null ? null : ((RenderAttachmentBlockEntity) be).getRenderAttachmentData();
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.fabric.api.rendering.data.v1;
import net.minecraft.block.entity.BlockEntity;
/**
* Interface for {@link BlockEntity}s which provide dynamic model state data.<p>
*
* Dynamic model state data is separate from BlockState, and will be
* cached during render chunk building on the main thread (safely) and accessible
* during chunk rendering on non-main threads.<p>
*
* For this reason, please ensure that all accesses to the passed model data are
* thread-safe. This can be achieved by, for example, passing a pre-generated
* immutable object, or ensuring all gets performed on the passed object are atomic
* and well-checked for unusual states.<p>
*/
@FunctionalInterface
public interface RenderAttachmentBlockEntity {
/**
* @return The model state data provided by this block entity. Can be null.
*/
Object getRenderAttachmentData();
}

View file

@ -0,0 +1,29 @@
/*
* 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.fabric.mixin.rendering.data;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.minecraft.block.entity.BlockEntity;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(BlockEntity.class)
public class MixinBlockEntity implements RenderAttachmentBlockEntity {
@Override
public Object getRenderAttachmentData() {
return null;
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.fabric.mixin.rendering.data;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.minecraft.world.ViewableWorld;
import org.spongepowered.asm.mixin.Mixin;
/** Make {@link ViewableWorld} implement {@link RenderAttachedBlockView}. */
@Mixin(ViewableWorld.class)
public interface MixinViewableWorld extends RenderAttachedBlockView {
}

View file

@ -0,0 +1,67 @@
/*
* 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.fabric.mixin.rendering.data.client;
import java.util.HashMap;
import java.util.Map;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
@Mixin(ChunkRendererRegion.class)
public abstract class MixinChunkRendererRegion implements RenderAttachedBlockView {
private HashMap<BlockPos, Object> fabric_renderDataObjects;
@Inject(at = @At("RETURN"), method = "<init>")
public void init(World world, int cxOff, int czOff, WorldChunk[][] chunks, BlockPos posFrom, BlockPos posTo, CallbackInfo info) {
HashMap<BlockPos, Object> map = new HashMap<>();
for (WorldChunk[] chunkA : chunks) {
for (WorldChunk chunkB : chunkA) {
for (Map.Entry<BlockPos, BlockEntity> entry: chunkB.getBlockEntities().entrySet()) {
BlockPos entPos = entry.getKey();
if (entPos.getX() >= posFrom.getX() && entPos.getX() <= posTo.getX()
&& entPos.getY() >= posFrom.getY() && entPos.getY() <= posTo.getY()
&& entPos.getZ() >= posFrom.getZ() && entPos.getZ() <= posTo.getZ()) {
Object o = ((RenderAttachmentBlockEntity) entry.getValue()).getRenderAttachmentData();
if (o != null) {
map.put(entPos, o);
}
}
}
}
}
this.fabric_renderDataObjects = map;
}
@Override
public Object getBlockEntityRenderAttachment(BlockPos pos) {
return fabric_renderDataObjects.get(pos);
}
}

View file

@ -0,0 +1,15 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.rendering.data",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinBlockEntity",
"MixinViewableWorld"
],
"client": [
"client.MixinChunkRendererRegion"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,13 @@
{
"schemaVersion": 1,
"id": "fabric-rendering-data-attachment-v1",
"version": "${version}",
"license": "Apache-2.0",
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"mixins": [
"fabric-rendering-data-attachment-v1.mixins.json"
]
}

View file

@ -28,7 +28,10 @@ include 'fabric-networking-v0'
include 'fabric-networking-blockentity-v0'
include 'fabric-object-builders-v0'
include 'fabric-registry-sync-v0'
include 'fabric-renderer-api-v1'
include 'fabric-renderer-indigo'
include 'fabric-rendering-v0'
include 'fabric-rendering-data-attachment-v1'
include 'fabric-rendering-fluids-v1'
include 'fabric-resource-loader-v0'
include 'fabric-tag-extensions-v0'