forked from FabricMC/fabric
Big thanks to Grondag and Player for all the pain and trouble we all went through.
This commit is contained in:
parent
45e1a1c86e
commit
02a46d5b29
76 changed files with 7125 additions and 0 deletions
6
fabric-networking/src/main/resources/fabric.mod.json
Normal file
6
fabric-networking/src/main/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-networking",
|
||||
"version": "${version}",
|
||||
"license": "Apache-2.0"
|
||||
}
|
6
fabric-renderer-api-v1/build.gradle
Normal file
6
fabric-renderer-api-v1/build.gradle
Normal 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')
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"required": false,
|
||||
"package": "net.fabricmc.fabric.mixin.renderer",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"client": [
|
||||
"client.MixinDebugHud"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
14
fabric-renderer-api-v1/src/main/resources/fabric.mod.json
Normal file
14
fabric-renderer-api-v1/src/main/resources/fabric.mod.json
Normal 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"
|
||||
]
|
||||
}
|
7
fabric-renderer-indigo/build.gradle
Normal file
7
fabric-renderer-indigo/build.gradle
Normal 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')
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
17
fabric-renderer-indigo/src/main/resources/fabric-renderer-indigo.mixins.json
Executable file
17
fabric-renderer-indigo/src/main/resources/fabric-renderer-indigo.mixins.json
Executable 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
|
||||
}
|
||||
}
|
19
fabric-renderer-indigo/src/main/resources/fabric.mod.json
Executable file
19
fabric-renderer-indigo/src/main/resources/fabric.mod.json
Executable 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"
|
||||
]
|
||||
}
|
||||
}
|
6
fabric-rendering-data-attachment-v1/build.gradle
Normal file
6
fabric-rendering-data-attachment-v1/build.gradle
Normal 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')
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue