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

CyclopsMC / IntegratedDynamics / 16552051255

27 Jul 2025 01:58PM UTC coverage: 53.206% (+8.0%) from 45.161%
16552051255

push

github

rubensworks
Resolve minor TODOs

2888 of 8740 branches covered (33.04%)

Branch coverage included in aggregate %.

17341 of 29280 relevant lines covered (59.22%)

3.08 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.google.common.primitives.Ints;
8
import com.mojang.math.Quadrant;
9
import net.minecraft.client.renderer.block.model.*;
10
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
11
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
12
import net.minecraft.client.resources.model.BlockModelRotation;
13
import net.minecraft.client.resources.model.ModelState;
14
import net.minecraft.core.BlockPos;
15
import net.minecraft.core.Direction;
16
import net.minecraft.util.RandomSource;
17
import net.minecraft.world.entity.LivingEntity;
18
import net.minecraft.world.item.ItemDisplayContext;
19
import net.minecraft.world.item.ItemStack;
20
import net.minecraft.world.level.BlockAndTintGetter;
21
import net.minecraft.world.level.Level;
22
import net.minecraft.world.level.block.state.BlockState;
23
import net.minecraft.world.phys.Vec3;
24
import net.neoforged.neoforge.client.ClientHooks;
25
import net.neoforged.neoforge.model.data.ModelData;
26
import org.apache.commons.lang3.tuple.Triple;
27
import org.cyclops.cyclopscore.client.model.DelegatingDynamicItemAndBlockModel;
28
import org.cyclops.cyclopscore.helper.IModHelpers;
29
import org.cyclops.cyclopscore.helper.ModelHelpers;
30
import org.cyclops.integrateddynamics.GeneralConfig;
31
import org.cyclops.integrateddynamics.api.part.PartRenderPosition;
32
import org.cyclops.integrateddynamics.block.BlockCableClientConfig;
33
import org.cyclops.integrateddynamics.core.blockentity.BlockEntityMultipartTicking;
34
import org.joml.Vector3f;
35

36
import java.util.List;
37
import java.util.Optional;
38
import java.util.concurrent.TimeUnit;
39
import java.util.stream.Collectors;
40

41
/**
42
 * A base dynamic facadeModel for cables.
43
 * @author rubensworks
44
 */
45
public abstract class CableModelBase extends DelegatingDynamicItemAndBlockModel {
46

47
    private static final FaceBakery FACE_BAKERY = new FaceBakery();
×
48
    private static final Cache<Triple<IRenderState, Direction, ChunkSectionLayer>, List<BakedQuad>> CACHE_QUADS = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build();
×
49

50
    private static final int RADIUS = 4;
51
    private static final int TEXTURE_SIZE = 16;
52

53
    private static final int LENGTH_CONNECTION = (TEXTURE_SIZE - RADIUS) / 2;
54
    private static final int LENGTH_CONNECTION_LIMITED = 1;
55
    private static final int INV_LENGTH_CONNECTION = TEXTURE_SIZE - LENGTH_CONNECTION;
56
    public static final float MIN = (float) LENGTH_CONNECTION / (float) TEXTURE_SIZE;
57
    public static final float MAX = 1.0F - MIN;
58
    private static final PartRenderPosition CABLE_RENDERPOSITION = new PartRenderPosition(-1,
×
59
            (((float) TEXTURE_SIZE - (float) RADIUS) / 2 / (float) TEXTURE_SIZE),
60
            (float) RADIUS / (float) TEXTURE_SIZE, (float) RADIUS / (float) TEXTURE_SIZE);
61

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

64
    protected static final ItemTransforms TRANSFORMS = ModelHelpers.modifyDefaultTransforms(ImmutableMap.of(
×
65
            ItemDisplayContext.FIRST_PERSON_LEFT_HAND, new ItemTransform(
66
                    new Vector3f(0, 45, 0),
67
                    new Vector3f(0, 1f / 32, 0),
68
                    new Vector3f(0.4F, 0.4F, 0.4F)),
69
            ItemDisplayContext.FIRST_PERSON_RIGHT_HAND, new ItemTransform(
70
                    new Vector3f(0, 225, 0),
71
                    new Vector3f(0, 1f / 32, 0),
72
                    new Vector3f(0.4F, 0.4F, 0.4F))
73
    ));
74

75
    public CableModelBase(BlockAndTintGetter level, BlockState blockState, Direction facing, RandomSource rand, ModelData modelData, ChunkSectionLayer renderType) {
76
        super(level, blockState, facing, rand, modelData, renderType);
×
77
    }
×
78

79
    public CableModelBase(ItemStack itemStack, Level world, LivingEntity entity) {
80
        super(itemStack, world, entity);
×
81
    }
×
82

83
    public CableModelBase() {
84
        super();
×
85
    }
×
86

87
    protected static float[][][] makeQuadVertexes(float min, float max, float length) {
88
        return new float[][][]{
×
89
                {
90
                        {min, length, min},
91
                        {max, length, min},
92
                        {max, max   , min},
93
                        {min, max   , min},
94
                },
95
                {
96
                        {min, max   , min},
97
                        {min, max   , max},
98
                        {min, length, max},
99
                        {min, length, min},
100
                },
101
                {
102
                        {min, max   , max},
103
                        {max, max   , max},
104
                        {max, length,  max},
105
                        {min, length, max},
106
                },
107
                {
108
                        {max, length, min},
109
                        {max, length, max},
110
                        {max, max   , max},
111
                        {max, max   , min},
112
                }
113
        };
114
    }
115

116
    private Direction getSideFromVecs(Vec3 a, Vec3 b, Vec3 c) {
117
        int dir = a.y == b.y && b.y == c.y ? 0 : (a.x == b.x && b.x == c.x ? 2 : 4);
×
118
        if (dir == 0) {
×
119
            dir += (c.y >= 0.5) ? 1 : 0;
×
120
        } else if (dir == 2) {
×
121
            dir += (c.x >= 0.5) ? 1 : 0;
×
122
        } else if (dir == 4) {
×
123
            dir += (c.z >= 0.5) ? 1 : 0;
×
124
        }
125
        return Direction.from3DDataValue(dir);
×
126
    }
127

128
    public List<BakedQuad> getFacadeQuads(BlockStateModel facadeModel, BlockState blockState, Direction side, PartRenderPosition partRenderPosition, ChunkSectionLayer renderType) {
129
        List<BakedQuad> originalQuads = Lists.newArrayList();
×
130
        for (BlockModelPart collectPart : facadeModel.collectParts(level, BlockPos.ZERO, blockState, rand)) {
×
131
            if (collectPart.getRenderType(blockState) == renderType) {
×
132
                originalQuads.addAll(collectPart.getQuads(null));
×
133
                for (Direction direction : Direction.values()) {
×
134
                    originalQuads.addAll(collectPart.getQuads(direction));
×
135
                }
136
            }
137
        }
×
138

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

179
    private void addFacadeQuad(List<BakedQuad> quads, BakedQuad originalQuad, float u0, float v0, float u1, float v1, Direction side) {
180
        Vector3f from = new Vector3f(u0 * 16f, v0 * 16f, 0f);
×
181
        Vector3f to = new Vector3f(u1 * 16f, v1 * 16f, 0f);
×
182
        TextureAtlasSprite texture = originalQuad.sprite();
×
183
        BlockElementFace.UVs blockFaceUV = new BlockElementFace.UVs(16f - u1 * 16f, 16f - v1 * 16f, 16f - u0 * 16f, 16f - v0 * 16f);
×
184
        Direction NO_FACE_CULLING = null;
×
185
        String DUMMY_TEXTURE_NAME = "";
×
186
        BlockElementFace blockPartFace = new BlockElementFace(NO_FACE_CULLING, originalQuad.tintIndex(), DUMMY_TEXTURE_NAME, blockFaceUV, Quadrant.R0);
×
187
        ModelState transformation = getRotation(side);
×
188
        BlockElementRotation DEFAULT_ROTATION = null;
×
189
        boolean APPLY_SHADING = true;
×
190
        quads.add(FACE_BAKERY.bakeQuad(from, to, blockPartFace, texture, Direction.NORTH, transformation, DEFAULT_ROTATION, APPLY_SHADING, 0));
×
191
    }
×
192

193
    public static BlockModelRotation getRotation(Direction facing) {
194
        switch (facing) {
×
195
            case DOWN:  return BlockModelRotation.X90_Y180;
×
196
            case UP:    return BlockModelRotation.X270_Y180;
×
197
            case NORTH: return BlockModelRotation.X0_Y0;
×
198
            case SOUTH: return BlockModelRotation.X0_Y180;
×
199
            case WEST:  return BlockModelRotation.X0_Y270;
×
200
            case EAST:  return BlockModelRotation.X0_Y90;
×
201
        }
202
        throw new IllegalArgumentException(String.valueOf(facing));
×
203
    }
204

205
    protected abstract boolean isRealCable(ModelData modelData);
206
    protected abstract Optional<BlockState> getFacade(ModelData modelData);
207
    protected abstract boolean isConnected(ModelData modelData, Direction side);
208
    protected abstract boolean hasPart(ModelData modelData, Direction side);
209
    protected abstract PartRenderPosition getPartRenderPosition(ModelData modelData, Direction side);
210
    protected abstract boolean shouldRenderParts(ModelData modelData);
211
    protected abstract BlockStateModel getPartModel(ModelData modelData, Direction side);
212
    protected abstract IRenderState getRenderState(ModelData modelData);
213

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

260
                            boolean invert = i == 2 || i == 1;
×
261
                            int length = hasPart ? LENGTH_CONNECTION_LIMITED : LENGTH_CONNECTION;
×
262

263
                            int[] data = Ints.concat(
×
264
                                    vertexToInts((float) v1.x, (float) v1.y, (float) v1.z, -1, texture,
×
265
                                            LENGTH_CONNECTION, invert ? length : 0),
×
266
                                    vertexToInts((float) v2.x, (float) v2.y, (float) v2.z, -1, texture,
×
267
                                            INV_LENGTH_CONNECTION, invert ? length : 0),
×
268
                                    vertexToInts((float) v3.x, (float) v3.y, (float) v3.z, -1, texture,
×
269
                                            INV_LENGTH_CONNECTION, invert ? 0 : length),
×
270
                                    vertexToInts((float) v4.x, (float) v4.y, (float) v4.z, -1, texture,
×
271
                                            LENGTH_CONNECTION, invert ? 0 : length)
×
272
                            );
273
                            i++;
×
274
                            ClientHooks.fillNormal(data); // This fixes lighting issues when item is rendered in hand/inventory
×
275
                            ret.add(new BakedQuad(data, -1, realSide, texture, true, 0, true));
×
276
                        }
277
                    } else {
×
278
                        addBakedQuad(ret, MIN, MAX, MIN, MAX, MAX, texture, side);
×
279
                    }
280
                }
281
            }
282

283
            if (blockStateHolder.isPresent() && shouldRenderParts(modelData) && this.renderType != null) {
×
284
                BlockStateModel facadeModel = IModHelpers.get().getRenderHelpers().getBakedModel(blockStateHolder.get());
×
285
                for (Direction side : Direction.values()) {
×
286
                    boolean isConnected = isItemStack() ? side == Direction.EAST || side == Direction.WEST : isConnected(modelData, side);
×
287
                    PartRenderPosition partRenderPosition = PartRenderPosition.NONE;
×
288
                    boolean hasPart = !isItemStack() && hasPart(modelData, side);
×
289
                    if (hasPart) partRenderPosition = getPartRenderPosition(modelData, side);
×
290
                    else if (isConnected) partRenderPosition = CABLE_RENDERPOSITION;
×
291
                    ret.addAll(getFacadeQuads(facadeModel, blockStateHolder.get(), side, partRenderPosition, this.renderType));
×
292
                }
293
            }
294

295
            // Close the cable connections for items
296
            if (isItemStack()) {
×
297
                addBakedQuad(ret, MIN, MAX, MIN, MAX, 1, texture, Direction.EAST);
×
298
                addBakedQuad(ret, MIN, MAX, MIN, MAX, 1, texture, Direction.WEST);
×
299
            }
300
            cachedQuads = ret;
×
301
            if (cacheKey != null) {
×
302
                CACHE_QUADS.put(cacheKey, cachedQuads);
×
303
            }
304
        }
305
        return cachedQuads;
×
306
    }
307

308
    @Override
309
    public TextureAtlasSprite particleIcon() {
310
        return BlockCableClientConfig.BLOCK_TEXTURE;
×
311
    }
312

313
    @Override
314
    public ModelData getModelData(BlockAndTintGetter world, BlockPos pos, BlockState state, ModelData tileData) {
315
        return IModHelpers.get().getBlockEntityHelpers().get(world, pos, BlockEntityMultipartTicking.class)
×
316
                .map(BlockEntityMultipartTicking::getConnectionState)
×
317
                .orElse(ModelData.EMPTY);
×
318
    }
319

320
    @Override
321
    public boolean usesBlockLight() {
322
        return false; // If false, RenderHelper.setupGuiFlatDiffuseLighting() is called
×
323
    }
324

325
    @Override
326
    public ItemTransforms getTopTransforms() {
327
        return TRANSFORMS;
×
328
    }
329

330
    @Override
331
    public List<ChunkSectionLayer> getRenderTypes(BlockState state, RandomSource rand, ModelData data) {
332
        return List.of(ChunkSectionLayer.values());
×
333
    }
334
}
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