-
Notifications
You must be signed in to change notification settings - Fork 7
/
GeoBlockRenderer.java
249 lines (209 loc) · 10.1 KB
/
GeoBlockRenderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/**
* This class is a fork of the matching class found in the Geckolib repository.
* Original source: https://github.com/bernie-g/geckolib
* Copyright © 2024 Bernie-G.
* Licensed under the MIT License.
* https://github.com/bernie-g/geckolib/blob/main/LICENSE
*/
package mod.azure.azurelib.renderer;
import java.util.List;
import mod.azure.azurelib.platform.Services;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import mod.azure.azurelib.cache.object.BakedGeoModel;
import mod.azure.azurelib.cache.object.GeoBone;
import mod.azure.azurelib.cache.texture.AnimatableTexture;
import mod.azure.azurelib.constant.DataTickets;
import mod.azure.azurelib.core.animatable.GeoAnimatable;
import mod.azure.azurelib.core.animation.AnimationState;
import mod.azure.azurelib.model.GeoModel;
import mod.azure.azurelib.renderer.layer.GeoRenderLayer;
import mod.azure.azurelib.renderer.layer.GeoRenderLayersContainer;
import mod.azure.azurelib.util.RenderUtils;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.DirectionalBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
/**
* Base {@link GeoRenderer} class for rendering {@link BlockEntity Blocks} specifically.<br>
* All blocks added to be rendered by AzureLib should use an instance of this class.
*/
public class GeoBlockRenderer<T extends BlockEntity & GeoAnimatable> implements GeoRenderer<T>, BlockEntityRenderer<T> {
protected final GeoModel<T> model;
protected final GeoRenderLayersContainer<T> renderLayers = new GeoRenderLayersContainer<>(this);
protected T animatable;
protected float scaleWidth = 1;
protected float scaleHeight = 1;
protected Matrix4f blockRenderTranslations = new Matrix4f();
protected Matrix4f modelRenderTranslations = new Matrix4f();
public GeoBlockRenderer(GeoModel<T> model) {
this.model = model;
}
/**
* Gets the model instance for this renderer
*/
@Override
public GeoModel<T> getGeoModel() {
return this.model;
}
/**
* Gets the {@link GeoAnimatable} instance currently being rendered
*/
@Override
public T getAnimatable() {
return this.animatable;
}
/**
* Gets the id that represents the current animatable's instance for animation purposes. This is mostly useful for things like items, which have a single registered instance for all objects
*/
@Override
public long getInstanceId(T animatable) {
return animatable.getBlockPos().hashCode();
}
/**
* Returns the list of registered {@link GeoRenderLayer GeoRenderLayers} for this renderer
*/
@Override
public List<GeoRenderLayer<T>> getRenderLayers() {
return this.renderLayers.getRenderLayers();
}
/**
* Adds a {@link GeoRenderLayer} to this renderer, to be called after the main model is rendered each frame
*/
public GeoBlockRenderer<T> addRenderLayer(GeoRenderLayer<T> renderLayer) {
this.renderLayers.addLayer(renderLayer);
return this;
}
/**
* Sets a scale override for this renderer, telling AzureLib to pre-scale the model
*/
public GeoBlockRenderer<T> withScale(float scale) {
return withScale(scale, scale);
}
/**
* Sets a scale override for this renderer, telling AzureLib to pre-scale the model
*/
public GeoBlockRenderer<T> withScale(float scaleWidth, float scaleHeight) {
this.scaleWidth = scaleWidth;
this.scaleHeight = scaleHeight;
return this;
}
/**
* Called before rendering the model to buffer. Allows for render modifications and preparatory work such as scaling and translating.<br>
* {@link PoseStack} translations made here are kept until the end of the render process
*/
@Override
public void preRender(PoseStack poseStack, T animatable, BakedGeoModel model, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
this.blockRenderTranslations = new Matrix4f(poseStack.last().pose());
scaleModelForRender(this.scaleWidth, this.scaleHeight, poseStack, animatable, model, isReRender, partialTick, packedLight, packedOverlay);
}
@Override
public void render(T animatable, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
this.animatable = animatable;
defaultRender(poseStack, this.animatable, bufferSource, null, null, 0, partialTick, packedLight);
}
/**
* The actual render method that subtype renderers should override to handle their specific rendering tasks.<br>
* {@link GeoRenderer#preRender} has already been called by this stage, and {@link GeoRenderer#postRender} will be called directly after
*/
@Override
public void actuallyRender(PoseStack poseStack, T animatable, BakedGeoModel model, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
if (!isReRender) {
AnimationState<T> animationState = new AnimationState<T>(animatable, 0, 0, partialTick, false);
long instanceId = getInstanceId(animatable);
animationState.setData(DataTickets.TICK, animatable.getTick(animatable));
animationState.setData(DataTickets.BLOCK_ENTITY, animatable);
this.model.addAdditionalStateData(animatable, instanceId, animationState::setData);
poseStack.translate(0.5, 0, 0.5);
rotateBlock(getFacing(animatable), poseStack);
this.model.handleAnimations(animatable, instanceId, animationState);
}
this.modelRenderTranslations = new Matrix4f(poseStack.last().pose());
RenderSystem.setShaderTexture(0, getTextureLocation(animatable));
GeoRenderer.super.actuallyRender(poseStack, animatable, model, renderType, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
}
/**
* Renders the provided {@link GeoBone} and its associated child bones
*/
@Override
public void renderRecursively(PoseStack poseStack, T animatable, GeoBone bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
if (bone.isTrackingMatrices()) {
Matrix4f poseState = new Matrix4f(poseStack.last().pose());
Matrix4f localMatrix = RenderUtils.invertAndMultiplyMatrices(poseState, this.blockRenderTranslations);
bone.setModelSpaceMatrix(RenderUtils.invertAndMultiplyMatrices(poseState, this.modelRenderTranslations));
bone.setLocalSpaceMatrix(RenderUtils.translateMatrix(localMatrix, getRenderOffset(this.animatable, 1).toVector3f()));
bone.setWorldSpaceMatrix(RenderUtils.translateMatrix(new Matrix4f(localMatrix), new Vector3f(this.animatable.getBlockPos().getX(), this.animatable.getBlockPos().getY(), this.animatable.getBlockPos().getZ())));
}
GeoRenderer.super.renderRecursively(poseStack, animatable, bone, renderType, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
}
public Vec3 getRenderOffset(BlockEntity entity, float f) {
return Vec3.ZERO;
}
/**
* Rotate the {@link PoseStack} based on the determined {@link Direction} the block is facing
*/
protected void rotateBlock(Direction facing, PoseStack poseStack) {
switch (facing) {
case SOUTH -> poseStack.mulPose(Axis.YP.rotationDegrees(180));
case WEST -> poseStack.mulPose(Axis.YP.rotationDegrees(90));
case NORTH -> poseStack.mulPose(Axis.YP.rotationDegrees(0));
case EAST -> poseStack.mulPose(Axis.YP.rotationDegrees(270));
case UP -> poseStack.mulPose(Axis.XP.rotationDegrees(90));
case DOWN -> poseStack.mulPose(Axis.XN.rotationDegrees(90));
}
}
/**
* Attempt to extract a direction from the block so that the model can be oriented correctly
*/
protected Direction getFacing(T block) {
BlockState blockState = block.getBlockState();
if (blockState.hasProperty(HorizontalDirectionalBlock.FACING))
return blockState.getValue(HorizontalDirectionalBlock.FACING);
if (blockState.hasProperty(DirectionalBlock.FACING))
return blockState.getValue(DirectionalBlock.FACING);
return Direction.NORTH;
}
/**
* Update the current frame of a {@link AnimatableTexture potentially animated} texture used by this GeoRenderer.<br>
* This should only be called immediately prior to rendering, and only
*
* @see AnimatableTexture#setAndUpdate(ResourceLocation, int)
*/
@Override
public void updateAnimatedTextureFrame(T animatable) {
AnimatableTexture.setAndUpdate(getTextureLocation(animatable), animatable.getBlockPos().getX() + animatable.getBlockPos().getY() + animatable.getBlockPos().getZ() + (int) animatable.getTick(animatable));
}
/**
* Create and fire the relevant {@code CompileLayers} event hook for this renderer
*/
@Override
public void fireCompileRenderLayersEvent() {
Services.GEO_RENDER_PHASE_EVENT_FACTORY.fireCompileBlockRenderLayers(this);
}
/**
* Create and fire the relevant {@code Pre-Render} event hook for this renderer.<br>
*
* @return Whether the renderer should proceed based on the cancellation state of the event
*/
@Override
public boolean firePreRenderEvent(PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) {
return Services.GEO_RENDER_PHASE_EVENT_FACTORY.fireBlockPreRender(this, poseStack, model, bufferSource, partialTick, packedLight);
}
/**
* Create and fire the relevant {@code Post-Render} event hook for this renderer
*/
@Override
public void firePostRenderEvent(PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) {
Services.GEO_RENDER_PHASE_EVENT_FACTORY.fireBlockPostRender(this, poseStack, model, bufferSource, partialTick, packedLight);
}
}