• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedDynamics / 24034486947

06 Apr 2026 01:51PM UTC coverage: 53.645% (-0.08%) from 53.721%
24034486947

push

github

rubensworks
Update to MC 26.1.1

3052 of 8931 branches covered (34.17%)

Branch coverage included in aggregate %.

18680 of 31580 relevant lines covered (59.15%)

3.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/org/cyclops/integrateddynamics/client/model/CableModelBase.java
1
package org.cyclops.integrateddynamics.client.model;
2

3
import com.google.common.cache.Cache;
4
import com.google.common.cache.CacheBuilder;
5
import com.google.common.collect.ImmutableMap;
6
import com.google.common.collect.Lists;
7
import com.mojang.math.Quadrant;
8
import net.minecraft.client.model.geom.builders.UVPair;
9
import net.minecraft.client.multiplayer.ClientLevel;
10
import net.minecraft.client.renderer.block.BlockAndTintGetter;
11
import net.minecraft.client.renderer.block.dispatch.BlockModelRotation;
12
import net.minecraft.client.renderer.block.dispatch.BlockStateModel;
13
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
14
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
15
import net.minecraft.client.renderer.rendertype.RenderTypes;
16
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
17
import net.minecraft.client.resources.model.ModelBaker;
18
import net.minecraft.client.renderer.block.dispatch.ModelState;
19
import net.minecraft.client.resources.model.cuboid.CuboidFace;
20
import net.minecraft.client.resources.model.cuboid.FaceBakery;
21
import net.minecraft.client.resources.model.cuboid.ItemTransform;
22
import net.minecraft.client.resources.model.cuboid.ItemTransforms;
23
import net.minecraft.client.resources.model.geometry.BakedQuad;
24
import net.minecraft.client.resources.model.sprite.Material;
25
import net.minecraft.core.BlockPos;
26
import net.minecraft.core.Direction;
27
import net.minecraft.util.RandomSource;
28
import net.minecraft.world.entity.ItemOwner;
29
import net.minecraft.world.item.ItemDisplayContext;
30
import net.minecraft.world.item.ItemStack;
31
import net.minecraft.world.level.block.state.BlockState;
32
import net.neoforged.neoforge.client.model.quad.BakedColors;
33
import net.neoforged.neoforge.client.model.quad.BakedNormals;
34
import net.neoforged.neoforge.model.data.ModelData;
35
import org.apache.commons.lang3.tuple.Triple;
36
import org.cyclops.cyclopscore.client.model.DelegatingDynamicItemAndBlockModel;
37
import org.cyclops.cyclopscore.helper.IModHelpers;
38
import org.cyclops.cyclopscore.helper.ModelHelpers;
39
import org.cyclops.integrateddynamics.GeneralConfig;
40
import org.cyclops.integrateddynamics.api.part.PartRenderPosition;
41
import org.cyclops.integrateddynamics.block.BlockCableClientConfig;
42
import org.cyclops.integrateddynamics.core.blockentity.BlockEntityMultipartTicking;
43
import org.joml.Vector3f;
44

45
import java.util.ArrayList;
46
import java.util.List;
47
import java.util.Optional;
48
import java.util.concurrent.TimeUnit;
49
import java.util.stream.Collectors;
50

51
/**
52
 * A base dynamic facadeModel for cables.
53
 * @author rubensworks
54
 */
55
public abstract class CableModelBase extends DelegatingDynamicItemAndBlockModel {
56

57
    public static ModelBaker MODEL_BAKER;
58
    private static final Cache<Triple<IRenderState, Direction, ChunkSectionLayer>, List<BakedQuad>> CACHE_QUADS = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build();
×
59

60
    private static final int RADIUS = 4;
61
    private static final int TEXTURE_SIZE = 16;
62

63
    private static final int LENGTH_CONNECTION = (TEXTURE_SIZE - RADIUS) / 2;
64
    private static final int LENGTH_CONNECTION_LIMITED = 1;
65
    private static final int INV_LENGTH_CONNECTION = TEXTURE_SIZE - LENGTH_CONNECTION;
66
    public static final float MIN = (float) LENGTH_CONNECTION / (float) TEXTURE_SIZE;
67
    public static final float MAX = 1.0F - MIN;
68
    private static final PartRenderPosition CABLE_RENDERPOSITION = new PartRenderPosition(-1,
×
69
            (((float) TEXTURE_SIZE - (float) RADIUS) / 2 / (float) TEXTURE_SIZE),
70
            (float) RADIUS / (float) TEXTURE_SIZE, (float) RADIUS / (float) TEXTURE_SIZE);
71

72
    private final float[][][] quadVertexes = makeQuadVertexes(MIN, MAX, 1.00F);
×
73

74
    protected static final ItemTransforms TRANSFORMS = ModelHelpers.modifyDefaultTransforms(ImmutableMap.of(
×
75
            ItemDisplayContext.FIRST_PERSON_LEFT_HAND, new ItemTransform(
76
                    new Vector3f(0, 45, 0),
77
                    new Vector3f(0, 1f / 32, 0),
78
                    new Vector3f(0.4F, 0.4F, 0.4F)),
79
            ItemDisplayContext.FIRST_PERSON_RIGHT_HAND, new ItemTransform(
80
                    new Vector3f(0, 225, 0),
81
                    new Vector3f(0, 1f / 32, 0),
82
                    new Vector3f(0.4F, 0.4F, 0.4F))
83
    ));
84

85
    public CableModelBase(BlockAndTintGetter level, BlockState blockState, Direction facing, RandomSource rand, ModelData modelData, ChunkSectionLayer renderType) {
86
        super(level, blockState, facing, rand, modelData, renderType);
×
87
    }
×
88

89
    public CableModelBase(ItemStack itemStack, ClientLevel world, ItemOwner entity) {
90
        super(itemStack, world, entity);
×
91
    }
×
92

93
    public CableModelBase() {
94
        super();
×
95
    }
×
96

97
    protected static float[][][] makeQuadVertexes(float min, float max, float length) {
98
        return new float[][][]{
×
99
                {
100
                        {min, length, min},
101
                        {max, length, min},
102
                        {max, max   , min},
103
                        {min, max   , min},
104
                },
105
                {
106
                        {min, max   , min},
107
                        {min, max   , max},
108
                        {min, length, max},
109
                        {min, length, min},
110
                },
111
                {
112
                        {min, max   , max},
113
                        {max, max   , max},
114
                        {max, length,  max},
115
                        {min, length, max},
116
                },
117
                {
118
                        {max, length, min},
119
                        {max, length, max},
120
                        {max, max   , max},
121
                        {max, max   , min},
122
                }
123
        };
124
    }
125

126
    private Direction getSideFromVecs(Vector3f a, Vector3f b, Vector3f c) {
127
        int dir = a.y == b.y && b.y == c.y ? 0 : (a.x == b.x && b.x == c.x ? 2 : 4);
×
128
        if (dir == 0) {
×
129
            dir += (c.y >= 0.5) ? 1 : 0;
×
130
        } else if (dir == 2) {
×
131
            dir += (c.x >= 0.5) ? 1 : 0;
×
132
        } else if (dir == 4) {
×
133
            dir += (c.z >= 0.5) ? 1 : 0;
×
134
        }
135
        return Direction.from3DDataValue(dir);
×
136
    }
137

138
    public List<BakedQuad> getFacadeQuads(BlockStateModel facadeModel, BlockState blockState, Direction side, PartRenderPosition partRenderPosition, ChunkSectionLayer renderType) {
139
        List<BakedQuad> originalQuads = Lists.newArrayList();
×
140
        List<BlockStateModelPart> parts = new ArrayList<>();
×
141
        facadeModel.collectParts(rand, parts);
×
142
        for (BlockStateModelPart collectPart : parts) {
×
143
            originalQuads.addAll(collectPart.getQuads(null));
×
144
            originalQuads.addAll(collectPart.getQuads(side));
×
145
        }
×
146

147
        return originalQuads.stream()
×
148
                .flatMap(originalQuad -> {
×
149
                    List<BakedQuad> ret = Lists.newLinkedList();
×
150
                    if(partRenderPosition == PartRenderPosition.NONE) {
×
151
                        addFacadeQuad(ret, originalQuad, 0, 0, 1f, 1f, side);
×
152
                    } else {
153
                        float w = partRenderPosition.getWidthFactorSide();
×
154
                        float h = partRenderPosition.getHeightFactorSide();
×
155
                        float u0 = 0f;
×
156
                        float v0 = 0f;
×
157
                        float u1 = (1f - w) / 2;
×
158
                        float v1 = (1f - h) / 2;
×
159
                        float u2 = u1 + w;
×
160
                        float v2 = v1 + h;
×
161
                        float u3 = 1f;
×
162
                        float v3 = 1f;
×
163
                        /*
164
                         * We render the following eight boxes, excluding the part box in the middle.
165
                         * -------
166
                         * |1|2|3|
167
                         * -------
168
                         * |4|P|5|
169
                         * -------
170
                         * |6|7|8|
171
                         * -------
172
                         */
173
                        addFacadeQuad(ret, originalQuad, u0, v0, u1, v1, side); // 1
×
174
                        addFacadeQuad(ret, originalQuad, u1, v0, u2, v1, side); // 2
×
175
                        addFacadeQuad(ret, originalQuad, u2, v0, u3, v1, side); // 3
×
176
                        addFacadeQuad(ret, originalQuad, u0, v1, u1, v2, side); // 4
×
177
                        addFacadeQuad(ret, originalQuad, u2, v1, u3, v2, side); // 5
×
178
                        addFacadeQuad(ret, originalQuad, u0, v2, u1, v3, side); // 6
×
179
                        addFacadeQuad(ret, originalQuad, u1, v2, u2, v3, side); // 7
×
180
                        addFacadeQuad(ret, originalQuad, u2, v2, u3, v3, side); // 8
×
181
                    }
182
                    return ret.stream();
×
183
                })
184
                .collect(Collectors.toList());
×
185
    }
186

187
    private void addFacadeQuad(List<BakedQuad> quads, BakedQuad originalQuad, float u0, float v0, float u1, float v1, Direction side) {
188
        Vector3f from = new Vector3f(u0 * 16f, v0 * 16f, 0f);
×
189
        Vector3f to = new Vector3f(u1 * 16f, v1 * 16f, 0f);
×
190
        TextureAtlasSprite texture = originalQuad.materialInfo().sprite();
×
191
        CuboidFace.UVs blockFaceUV = new CuboidFace.UVs(16f - u1 * 16f, 16f - v1 * 16f, 16f - u0 * 16f, 16f - v0 * 16f);
×
192
        Direction NO_FACE_CULLING = null;
×
193
        String DUMMY_TEXTURE_NAME = "";
×
194
        CuboidFace blockPartFace = new CuboidFace(NO_FACE_CULLING, originalQuad.materialInfo().tintIndex(), DUMMY_TEXTURE_NAME, blockFaceUV, Quadrant.R0);
×
195
        ModelState transformation = getRotation(side);
×
196
        boolean APPLY_SHADING = true;
×
197
        quads.add(FaceBakery.bakeQuad(MODEL_BAKER, from, to, blockPartFace, new Material.Baked(texture, false), Direction.NORTH, transformation, null, APPLY_SHADING, 0));
×
198
    }
×
199

200
    public static BlockModelRotation getRotation(Direction facing) {
201
        switch (facing) {
×
202
            case DOWN:  return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R90, Quadrant.R180));
×
203
            case UP:    return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R270, Quadrant.R180));
×
204
            case NORTH: return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R0, Quadrant.R0));
×
205
            case SOUTH: return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R0, Quadrant.R180));
×
206
            case WEST:  return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R0, Quadrant.R270));
×
207
            case EAST:  return BlockModelRotation.get(Quadrant.fromXYAngles(Quadrant.R0, Quadrant.R90));
×
208
        }
209
        throw new IllegalArgumentException(String.valueOf(facing));
×
210
    }
211

212
    protected abstract boolean isRealCable(ModelData modelData);
213
    protected abstract Optional<BlockState> getFacade(ModelData modelData);
214
    protected abstract boolean isConnected(ModelData modelData, Direction side);
215
    protected abstract boolean hasPart(ModelData modelData, Direction side);
216
    protected abstract PartRenderPosition getPartRenderPosition(ModelData modelData, Direction side);
217
    protected abstract boolean shouldRenderParts(ModelData modelData);
218
    protected abstract BlockStateModel getPartModel(ModelData modelData, Direction side);
219
    protected abstract IRenderState getRenderState(ModelData modelData);
220

221
    @Override
222
    public int materialFlags() {
223
        return 0;
×
224
    }
225

226
    @Override
227
    public List<BakedQuad> getGeneralQuads() {
228
        Triple<IRenderState, Direction, ChunkSectionLayer> cacheKey = null;
×
229
        List<BakedQuad> cachedQuads = null;
×
230
        if (GeneralConfig.cacheCableModels) {
×
231
            IRenderState renderState = getRenderState(modelData);
×
232
            if (renderState != null) {
×
233
                cacheKey = Triple.of(renderState, this.facing, this.renderType);
×
234
                cachedQuads = CACHE_QUADS.getIfPresent(cacheKey);
×
235
            }
236
        }
237
        if (cachedQuads == null) {
×
238
            List<BakedQuad> ret = Lists.newLinkedList();
×
239
            TextureAtlasSprite texture = particleIcon();
×
240
            Optional<BlockState> blockStateHolder = getFacade(modelData);
×
241
            boolean renderCable = isItemStack() || (isRealCable(modelData) && (
×
242
                    (!blockStateHolder.isPresent() && this.renderType == ChunkSectionLayer.SOLID)
×
243
                            || (blockStateHolder.isPresent() && this.renderType == ChunkSectionLayer.TRANSLUCENT)));
×
244
            for (Direction side : Direction.values()) {
×
245
                boolean isConnected = isItemStack() ? side == Direction.EAST || side == Direction.WEST : isConnected(modelData, side);
×
246
                boolean hasPart = !isItemStack() && hasPart(modelData, side);
×
247
                if (hasPart && shouldRenderParts(modelData)) {
×
248
                    try {
249
                        List<BlockStateModelPart> parts = new ArrayList<>();
×
250
                        getPartModel(modelData, side).collectParts(rand, parts);
×
251
                        for (BlockStateModelPart collectPart : parts) {
×
252
                            ret.addAll(collectPart.getQuads(null));
×
253
                        }
×
254
                    } catch (Exception e) {
×
255
                        // Skip rendering this part, could occur when the player is still logging in.
256
                    }
×
257
                }
258
                if (renderCable) {
×
259
                    if (isConnected || hasPart) {
×
260
                        int i = 0;
×
261
                        float[][][] quadVertexes = this.quadVertexes;
×
262
                        if (hasPart) {
×
263
                            PartRenderPosition partRenderPosition = getPartRenderPosition(modelData, side);
×
264
                            float depthFactor = partRenderPosition == PartRenderPosition.NONE ? 0F : partRenderPosition.getDepthFactor();
×
265
                            quadVertexes = makeQuadVertexes(MIN, MAX, 1F - depthFactor);
×
266
                        }
267
                        for (float[][] v : quadVertexes) {
×
268
                            Vector3f v1 = rotate(new Vector3f(v[0][0] - .5f, v[0][1] - .5f, v[0][2] - .5f), side).add(.5f, .5f, .5f);
×
269
                            Vector3f v2 = rotate(new Vector3f(v[1][0] - .5f, v[1][1] - .5f, v[1][2] - .5f), side).add(.5f, .5f, .5f);
×
270
                            Vector3f v3 = rotate(new Vector3f(v[2][0] - .5f, v[2][1] - .5f, v[2][2] - .5f), side).add(.5f, .5f, .5f);
×
271
                            Vector3f v4 = rotate(new Vector3f(v[3][0] - .5f, v[3][1] - .5f, v[3][2] - .5f), side).add(.5f, .5f, .5f);
×
272
                            Direction realSide = getSideFromVecs(v1, v2, v3);
×
273

274
                            boolean invert = i == 2 || i == 1;
×
275
                            int length = hasPart ? LENGTH_CONNECTION_LIMITED : LENGTH_CONNECTION;
×
276

277
                            i++;
×
278
                            ret.add(new BakedQuad(
×
279
                                    v1,
280
                                    v2,
281
                                    v3,
282
                                    v4,
283
                                    UVPair.pack(texture.getU(LENGTH_CONNECTION / 16f), texture.getV(invert ? length / 16f : 0)),
×
284
                                    UVPair.pack(texture.getU(INV_LENGTH_CONNECTION / 16f), texture.getV(invert ? length / 16f : 0)),
×
285
                                    UVPair.pack(texture.getU(INV_LENGTH_CONNECTION / 16f), texture.getV(invert ? 0 : length / 16f)),
×
286
                                    UVPair.pack(texture.getU(LENGTH_CONNECTION / 16f), texture.getV(invert ? 0 : length / 16f)),
×
287
                                    realSide,
288
                                    new BakedQuad.MaterialInfo(texture, ChunkSectionLayer.SOLID, RenderTypes.entityCutout(texture.atlasLocation()), -1, true, 0),
×
289
                                    BakedNormals.UNSPECIFIED,
290
                                    BakedColors.DEFAULT
291
                            ));
292
                        }
293
                    } else {
×
294
                        addBakedQuad(ret, MIN, MAX, MIN, MAX, MAX, texture, side);
×
295
                    }
296
                }
297
            }
298

299
            if (blockStateHolder.isPresent() && shouldRenderParts(modelData) && this.renderType != null && this.facing != null) {
×
300
                BlockStateModel facadeModel = IModHelpers.get().getRenderHelpers().getBakedModel(blockStateHolder.get());
×
301
                boolean isConnected = isItemStack() ? this.facing == Direction.EAST || this.facing == Direction.WEST : isConnected(modelData, this.facing);
×
302
                PartRenderPosition partRenderPosition = PartRenderPosition.NONE;
×
303
                boolean hasPart = !isItemStack() && hasPart(modelData, this.facing);
×
304
                if (hasPart) partRenderPosition = getPartRenderPosition(modelData, this.facing);
×
305
                else if (isConnected) partRenderPosition = CABLE_RENDERPOSITION;
×
306
                ret.addAll(getFacadeQuads(facadeModel, blockStateHolder.get(), this.facing, partRenderPosition, this.renderType));
×
307
            }
308

309
            // Close the cable connections for items
310
            if (isItemStack()) {
×
311
                addBakedQuad(ret, MIN, MAX, MIN, MAX, 1, texture, Direction.EAST);
×
312
                addBakedQuad(ret, MIN, MAX, MIN, MAX, 1, texture, Direction.WEST);
×
313
            }
314
            cachedQuads = ret;
×
315
            if (cacheKey != null) {
×
316
                CACHE_QUADS.put(cacheKey, cachedQuads);
×
317
            }
318
        }
319
        return cachedQuads;
×
320
    }
321

322
    public TextureAtlasSprite particleIcon() {
323
        return BlockCableClientConfig.BLOCK_TEXTURE;
×
324
    }
325

326
    @Override
327
    public Material.Baked particleMaterial() {
328
        return new Material.Baked(particleIcon(), false);
×
329
    }
330

331
    @Override
332
    public ModelData getModelData(BlockAndTintGetter world, BlockPos pos, BlockState state, ModelData tileData) {
333
        return IModHelpers.get().getBlockEntityHelpers().get(world, pos, BlockEntityMultipartTicking.class)
×
334
                .map(BlockEntityMultipartTicking::getConnectionState)
×
335
                .orElse(ModelData.EMPTY);
×
336
    }
337

338
    @Override
339
    public boolean usesBlockLight() {
340
        return false; // If false, RenderHelper.setupGuiFlatDiffuseLighting() is called
×
341
    }
342

343
    @Override
344
    public ItemTransforms getTopTransforms() {
345
        return TRANSFORMS;
×
346
    }
347

348
    @Override
349
    public List<ChunkSectionLayer> getRenderTypes(BlockState state, RandomSource rand, ModelData data) {
350
        return List.of(ChunkSectionLayer.values());
×
351
    }
352
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc