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

CyclopsMC / IntegratedDynamics / 30759991625

02 Aug 2026 05:56PM UTC coverage: 53.855% (-0.1%) from 53.973%
30759991625

push

github

rubensworks
Merge remote-tracking branch 'origin/master-1.21-lts' into master-26-lts

3101 of 9033 branches covered (34.33%)

Branch coverage included in aggregate %.

18945 of 31903 relevant lines covered (59.38%)

3.08 hits per line

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

20.34
/src/main/java/org/cyclops/integrateddynamics/core/block/VoxelShapeComponents.java
1
package org.cyclops.integrateddynamics.core.block;
2

3
import com.google.common.collect.Lists;
4
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
5
import it.unimi.dsi.fastutil.doubles.DoubleList;
6
import net.minecraft.core.AxisCycle;
7
import net.minecraft.core.BlockPos;
8
import net.minecraft.core.Direction;
9
import net.minecraft.world.InteractionHand;
10
import net.minecraft.world.InteractionResult;
11
import net.minecraft.world.entity.Entity;
12
import net.minecraft.world.entity.LivingEntity;
13
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
14
import net.minecraft.world.entity.ai.attributes.Attributes;
15
import net.minecraft.world.entity.player.Player;
16
import net.minecraft.world.item.ItemStack;
17
import net.minecraft.world.level.BlockGetter;
18
import net.minecraft.world.level.Level;
19
import net.minecraft.world.level.block.state.BlockState;
20
import net.minecraft.world.phys.AABB;
21
import net.minecraft.world.phys.BlockHitResult;
22
import net.minecraft.world.phys.Vec3;
23
import net.minecraft.world.phys.shapes.CollisionContext;
24
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
25
import net.minecraft.world.phys.shapes.Shapes;
26
import net.minecraft.world.phys.shapes.VoxelShape;
27
import org.apache.commons.lang3.tuple.Pair;
28
import org.cyclops.integrateddynamics.api.block.cable.ICableRayTraceHandler;
29
import org.cyclops.integrateddynamics.block.shapes.CollisionContextBlockSupport;
30
import org.cyclops.integrateddynamics.core.block.cable.CableRayTraceHandlers;
31

32
import javax.annotation.Nullable;
33
import java.util.Collection;
34
import java.util.Iterator;
35
import java.util.List;
36
import java.util.stream.Collectors;
37

38
/**
39
 * A {@link VoxelShape} that contains one or more {@link VoxelShapeComponents.IComponent}.
40
 *
41
 * These components are used to handle ray tracing on seperate components.
42
 * Consequently, ray trace results on this object may be safely cast to {@link BlockRayTraceResultComponent}
43
 * so that the targeted component may be retrieved.
44
 *
45
 * @author rubensworks
46
 */
47
public class VoxelShapeComponents extends VoxelShape implements Iterable<VoxelShape> {
48

49
    private final Collection<Pair<VoxelShape, IComponent>> entries;
50
    private final String stateId;
51

52
    protected VoxelShapeComponents(Collection<Pair<VoxelShape, IComponent>> entries, String stateId) {
53
        super(createInnerPart(entries));
4✔
54
        this.entries = entries;
3✔
55
        this.stateId = stateId;
3✔
56
    }
1✔
57

58
    protected static DiscreteVoxelShape createInnerPart(Collection<Pair<VoxelShape, IComponent>> entries) {
59
        return new Part(entries.stream()
6✔
60
                .map(pair -> pair.getLeft().shape)
6✔
61
                .collect(Collectors.toList()));
4✔
62
    }
63

64
    public static VoxelShapeComponents create(BlockState blockState, BlockGetter world, BlockPos blockPos,
65
                                              CollisionContext selectionContext, List<IComponent> components) {
66
        List<Pair<VoxelShape, IComponent>> entries = Lists.newArrayList();
2✔
67
        for (VoxelShapeComponents.IComponent component : components) {
10✔
68
            VoxelShape shape = component.getShape(blockState, world, blockPos, selectionContext);
7✔
69
            entries.add(Pair.of(shape, component));
6✔
70
        }
1✔
71

72
        StringBuilder stateIdBuilder = new StringBuilder();
4✔
73
        for (IComponent component : components) {
10✔
74
            stateIdBuilder.append(component.getStateId(blockState, world, blockPos));
8✔
75
            stateIdBuilder.append(";");
4✔
76
        }
1✔
77
        stateIdBuilder.append(selectionContext instanceof CollisionContextBlockSupport);
5✔
78

79
        return new VoxelShapeComponents(entries, stateIdBuilder.toString());
7✔
80
    }
81

82
    public String getStateId() {
83
        return this.stateId;
3✔
84
    }
85

86
    @Override
87
    public Iterator<VoxelShape> iterator() {
88
        return entries.stream().map(Pair::getLeft).iterator();
7✔
89
    }
90

91
    @Override
92
    public double min(Direction.Axis axis) {
93
        boolean first = true;
×
94
        double startMin = 0;
×
95
        for (VoxelShape shape : this) {
×
96
            double start = shape.min(axis);
×
97
            if (first || start < startMin) {
×
98
                startMin = start;
×
99
                first = false;
×
100
            }
101
        }
×
102
        return startMin;
×
103
    }
104

105
    @Override
106
    public double max(Direction.Axis axis) {
107
        boolean first = true;
×
108
        double endMax = 0;
×
109
        for (VoxelShape shape : this) {
×
110
            double end = shape.max(axis);
×
111
            if (first || end > endMax) {
×
112
                endMax = end;
×
113
                first = false;
×
114
            }
115
        }
×
116
        return endMax;
×
117
    }
118

119
    @Override
120
    public DoubleList getCoords(Direction.Axis axis) {
121
        DoubleArrayList values = new DoubleArrayList();
×
122
        for (VoxelShape shape : this) {
×
123
            values.addAll(shape.getCoords(axis));
×
124
        }
×
125
        return values;
×
126
    }
127

128
    @Override
129
    public boolean isEmpty() {
130
        for (VoxelShape shape : this) {
×
131
            if (!shape.isEmpty()) {
×
132
                return false;
×
133
            }
134
        }
×
135
        return true;
×
136
    }
137

138
    @Override
139
    public VoxelShape move(double x, double y, double z) {
140
        List<Pair<VoxelShape, IComponent>> entries = Lists.newArrayList();
×
141
        for (Pair<VoxelShape, IComponent> entry : this.entries) {
×
142
            entries.add(Pair.of(entry.getLeft().move(x, y, z), entry.getRight()));
×
143
        }
×
144
        return new VoxelShapeComponents(entries, this.stateId);
×
145
    }
146

147
    @Override
148
    public void forAllEdges(Shapes.DoubleLineConsumer consumer) {
149
        for (VoxelShape shape : this) {
×
150
            shape.forAllEdges(consumer);
×
151
        }
×
152
    }
×
153

154
    @Override
155
    public void forAllBoxes(Shapes.DoubleLineConsumer consumer) {
156
        for (VoxelShape shape : this) {
×
157
            shape.forAllBoxes(consumer);
×
158
        }
×
159
    }
×
160

161
    @Override
162
    public double max(Direction.Axis axis, double a, double b) {
163
        boolean first = true;
×
164
        double valueMax = 0D;
×
165
        for (VoxelShape shape : this) {
×
166
            double value = shape.max(axis, a, b);
×
167
            if (first || value > valueMax) {
×
168
                valueMax = value;
×
169
                first = false;
×
170
            }
171
        }
×
172
        return valueMax;
×
173
    }
174

175
    @Nullable
176
    @Override
177
    public BlockRayTraceResultComponent clip(Vec3 startVec, Vec3 endVec, BlockPos pos) {
178
        // Find component with shape that is closest to the startVec
179
        double distanceMin = Double.POSITIVE_INFINITY;
2✔
180
        VoxelShapeComponents.IComponent componentMin = null;
2✔
181
        BlockHitResult resultMin = null;
2✔
182

183
        for (Pair<VoxelShape, IComponent> entry : entries) {
11✔
184
            VoxelShape shape = entry.getLeft();
4✔
185
            BlockHitResult result = shape.clip(startVec, endVec, pos);
6✔
186
            if (result != null) {
2✔
187
                double distance = result.getLocation().distanceToSqr(startVec);
5✔
188
                if (distance < distanceMin) {
4✔
189
                    // If the previous match was a part and the current one is a facade,
190
                    // check if the direction of that part matches with the matched face of the facade,
191
                    // and if so, don't match the facade,
192
                    // because we want the user to be able to select parts within facades.
193
                    if (resultMin == null || !(entry.getRight().isRaytraceLastForFace() && componentMin.getRaytraceDirection() == result.getDirection())) {
7!
194
                        distanceMin = distance;
2✔
195
                        componentMin = entry.getRight();
4✔
196
                        resultMin = result;
2✔
197
                    }
198
                }
199
            }
200
        }
1✔
201

202
        // Store the component in the ray trace result when we found one.
203
        if (resultMin != null) {
2!
204
            return new BlockRayTraceResultComponent(resultMin, componentMin);
6✔
205
        }
206

207
        return null;
×
208
    }
209

210
    // A modified version of #clip, but with a Vec3 param
211
    @Nullable
212
    public BlockRayTraceResultComponent clipVec(Vec3 startVec, Vec3 endVec, BlockPos pos, Vec3 posVec) {
213
        // Find component with shape that is closest to the startVec
214
        double distanceMin = Double.POSITIVE_INFINITY;
×
215
        VoxelShapeComponents.IComponent componentMin = null;
×
216
        BlockHitResult resultMin = null;
×
217

218
        for (Pair<VoxelShape, IComponent> entry : entries) {
×
219
            VoxelShape shape = entry.getLeft();
×
220
            BlockHitResult result = shapeClipVec(shape, startVec, endVec, pos, posVec);
×
221
            if (result != null) {
×
222
                double distance = result.getLocation().distanceToSqr(startVec);
×
223
                if (distance < distanceMin) {
×
224
                    // If the previous match was a part and the current one is a facade,
225
                    // check if the direction of that part matches with the matched face of the facade,
226
                    // and if so, don't match the facade,
227
                    // because we want the user to be able to select parts within facades.
228
                    if (resultMin == null || !(entry.getRight().isRaytraceLastForFace() && componentMin.getRaytraceDirection() == result.getDirection())) {
×
229
                        distanceMin = distance;
×
230
                        componentMin = entry.getRight();
×
231
                        resultMin = result;
×
232
                    }
233
                }
234
            }
235
        }
×
236

237
        // Store the component in the ray trace result when we found one.
238
        if (resultMin != null) {
×
239
            return new BlockRayTraceResultComponent(resultMin, componentMin);
×
240
        }
241

242
        return null;
×
243
    }
244

245
    @Nullable // A modified version of VoxelShape#clip, but with a Vec3 param
246
    public BlockHitResult shapeClipVec(VoxelShape shape, Vec3 startVec, Vec3 endVec, BlockPos pos, Vec3 posVec) {
247
        if (shape.isEmpty()) {
×
248
            return null;
×
249
        } else {
250
            Vec3 vec3 = endVec.subtract(startVec);
×
251
            if (vec3.lengthSqr() < 1.0E-7) {
×
252
                return null;
×
253
            } else {
254
                Vec3 vec31 = startVec.add(vec3.scale(0.001));
×
255
                return shape.shape
×
256
                        .isFullWide(
×
257
                                shape.findIndex(Direction.Axis.X, vec31.x - posVec.x()),
×
258
                                shape.findIndex(Direction.Axis.Y, vec31.y - posVec.y()),
×
259
                                shape.findIndex(Direction.Axis.Z, vec31.z - posVec.z())
×
260
                        )
261
                        ? new BlockHitResult(vec31, Direction.getApproximateNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true)
×
262
                        : aabbClip(shape.toAabbs(), startVec, endVec, pos, posVec);
×
263
            }
264
        }
265
    }
266

267
    @Nullable // A modified version of AABB#clip, but with a Vec3 param
268
    public static BlockHitResult aabbClip(Iterable<AABB> boxes, Vec3 start, Vec3 end, BlockPos pos, Vec3 posVec) {
269
        double[] adouble = new double[]{1.0};
×
270
        Direction direction = null;
×
271
        double d0 = end.x - start.x;
×
272
        double d1 = end.y - start.y;
×
273
        double d2 = end.z - start.z;
×
274

275
        for (AABB aabb : boxes) {
×
276
            direction = AABB.getDirection(aabb.move(posVec), start, adouble, direction, d0, d1, d2);
×
277
        }
×
278

279
        if (direction == null) {
×
280
            return null;
×
281
        } else {
282
            double d3 = adouble[0];
×
283
            return new BlockHitResult(start.add(d3 * d0, d3 * d1, d3 * d2), direction, pos, false);
×
284
        }
285
    }
286

287
    /**
288
     * Do a ray trace for the current look direction of the player.
289
     * @param pos The block position to perform a ray trace for.
290
     * @param entity The entity.
291
     * @return A holder object with information on the ray tracing.
292
     */
293
    @Nullable
294
    public BlockRayTraceResultComponent rayTrace(BlockPos pos, @Nullable Entity entity) {
295
        for (ICableRayTraceHandler handler : CableRayTraceHandlers.REGISTRY.getHandlers()) {
7!
296
            if (handler.canHandle(pos, entity)) {
×
297
                return handler.rayTrace(pos, entity, this::rayTraceInnerVec);
×
298
            }
299
        }
×
300
        return rayTraceInner(pos, entity);
5✔
301
    }
302

303
    /**
304
     * Do a ray trace for the current look direction of the player.
305
     * @param pos The block position to perform a ray trace for.
306
     * @param entity The entity.
307
     * @return A holder object with information on the ray tracing.
308
     */
309
    @Nullable
310
    protected BlockRayTraceResultComponent rayTraceInner(BlockPos pos, @Nullable Entity entity) {
311
        if(entity == null) {
2✔
312
            return null;
2✔
313
        }
314
        AttributeInstance reachDistanceAttribute = entity instanceof LivingEntity ? ((LivingEntity) entity).getAttribute(Attributes.BLOCK_INTERACTION_RANGE) : null;
9!
315
        double reachDistance = reachDistanceAttribute == null ? 5 : reachDistanceAttribute.getValue();
5!
316

317
        double eyeHeight = entity.level().isClientSide() ? entity.getEyeHeight() : entity.getEyeHeight(); // Client removed :  - player.getDefaultEyeHeight()
8!
318
        Vec3 lookVec = entity.getLookAngle();
3✔
319
        Vec3 origin = new Vec3(entity.getX(), entity.getY() + eyeHeight, entity.getZ());
12✔
320
        Vec3 direction = origin.add(lookVec.x * reachDistance, lookVec.y * reachDistance, lookVec.z * reachDistance);
15✔
321

322
        return clip(origin, direction, pos);
6✔
323
    }
324

325
    /**
326
     * Do a ray trace for the current look direction of the player.
327
     * @param pos The block position to perform a ray trace for.
328
     * @param entity The entity.
329
     * @return A holder object with information on the ray tracing.
330
     */
331
    @Nullable
332
    protected BlockRayTraceResultComponent rayTraceInnerVec(BlockPos pos, @Nullable Entity entity, Vec3 posVec) {
333
        if(entity == null) {
×
334
            return null;
×
335
        }
336
        AttributeInstance reachDistanceAttribute = entity instanceof LivingEntity ? ((LivingEntity) entity).getAttribute(Attributes.BLOCK_INTERACTION_RANGE) : null;
×
337
        double reachDistance = reachDistanceAttribute == null ? 5 : reachDistanceAttribute.getValue();
×
338

339
        double eyeHeight = entity.level().isClientSide() ? entity.getEyeHeight() : entity.getEyeHeight(); // Client removed :  - player.getDefaultEyeHeight()
×
340
        Vec3 lookVec = entity.getLookAngle();
×
341
        Vec3 origin = new Vec3(entity.getX(), entity.getY() + eyeHeight, entity.getZ());
×
342
        Vec3 direction = origin.add(lookVec.x * reachDistance, lookVec.y * reachDistance, lookVec.z * reachDistance);
×
343

344
        return clipVec(origin, direction, pos, posVec);
×
345
    }
346

347
    @Override
348
    public double collideX(AxisCycle rotation, AABB axisAlignedBB, double range) {
349
        boolean first = true;
×
350
        double valueBest = 0D;
×
351
        for (VoxelShape shape : this) {
×
352
            double value = shape.collideX(rotation, axisAlignedBB, range);
×
353
            if (range > 0) {
×
354
                if (first || value < valueBest) {
×
355
                    valueBest = value;
×
356
                    first = false;
×
357
                }
358
            } else {
359
                if (first || value > valueBest) {
×
360
                    valueBest = value;
×
361
                    first = false;
×
362
                }
363
            }
364
        }
×
365
        return valueBest;
×
366
    }
367

368
    public static class Part extends DiscreteVoxelShape implements Iterable<DiscreteVoxelShape> {
369

370
        private final Collection<DiscreteVoxelShape> entries;
371

372
        public Part(Collection<DiscreteVoxelShape> entries) {
373
            super(0, 0, 0);
5✔
374
            this.entries = entries;
3✔
375
        }
1✔
376

377
        @Override
378
        public Iterator<DiscreteVoxelShape> iterator() {
379
            return entries.iterator();
×
380
        }
381

382
        @Override
383
        public boolean isFullWide(int x, int y, int z) {
384
            for (DiscreteVoxelShape part : this) {
×
385
                if (part.isFullWide(x, y, z)) {
×
386
                    return true;
×
387
                }
388
            }
×
389
            return false;
×
390
        }
391

392
        @Override
393
        public boolean isFull(int x, int y, int z) {
394
            for (DiscreteVoxelShape part : this) {
×
395
                if (part.isFull(x, y, z)) {
×
396
                    return true;
×
397
                }
398
            }
×
399
            return false;
×
400
        }
401

402
        @Override
403
        public void fill(int x, int y, int z) {
404
            for (DiscreteVoxelShape part : this) {
×
405
                part.fill(x, y, z);
×
406
            }
×
407
        }
×
408

409
        @Override
410
        public int firstFull(Direction.Axis axis) {
411
            boolean first = true;
×
412
            int startMin = 0;
×
413
            for (DiscreteVoxelShape part : this) {
×
414
                int start = part.firstFull(axis);
×
415
                if (first || start < startMin) {
×
416
                    startMin = start;
×
417
                    first = false;
×
418
                }
419
            }
×
420
            return startMin;
×
421
        }
422

423
        @Override
424
        public int lastFull(Direction.Axis axis) {
425
            boolean first = true;
×
426
            int endMax = 0;
×
427
            for (DiscreteVoxelShape part : this) {
×
428
                int end = part.lastFull(axis);
×
429
                if (first || end > endMax) {
×
430
                    endMax = end;
×
431
                    first = false;
×
432
                }
433
            }
×
434
            return endMax;
×
435
        }
436

437
        @Override
438
        public int getSize(Direction.Axis axis) {
439
            boolean first = true;
×
440
            int sizeMax = 0;
×
441
            for (DiscreteVoxelShape part : this) {
×
442
                int size = part.getSize(axis);
×
443
                if (first || size > sizeMax) {
×
444
                    sizeMax = size;
×
445
                    first = false;
×
446
                }
447
            }
×
448
            return sizeMax;
×
449
        }
450

451
        @Override
452
        public void forAllBoxes(IntLineConsumer consumer, boolean p_197831_2_) {
453
            for (DiscreteVoxelShape part : this) {
×
454
                part.forAllBoxes(consumer, p_197831_2_);
×
455
            }
×
456
        }
×
457
    }
458

459
    public static interface IComponent {
460

461
        /**
462
         * @param blockState The block state.
463
         * @param world The world.
464
         * @param blockPos The position.
465
         * @return Unique identifier for the component's state.
466
         */
467
        public String getStateId(BlockState blockState, BlockGetter world, BlockPos blockPos);
468

469
        /**
470
         * Get the shape of this component.
471
         * @param blockState The block state.
472
         * @param world The world.
473
         * @param blockPos The position.
474
         * @param selectionContext The selection context.
475
         * @return The shape.
476
         */
477
        public VoxelShape getShape(BlockState blockState, BlockGetter world, BlockPos blockPos, CollisionContext selectionContext);
478

479
        /**
480
         * Get the pick block item.
481
         * @param world The world
482
         * @param pos The position
483
         * @return The item.
484
         */
485
        public ItemStack getCloneItemStack(Level world, BlockPos pos);
486

487
        /**
488
         * Destroy this component
489
         * @param world The world
490
         * @param pos The position
491
         * @param player The player destroying the component.
492
         * @param saveState If the component state should be saved in the dropped item.
493
         * @return If the complete block was destroyed
494
         */
495
        public boolean destroy(Level world, BlockPos pos, Player player, boolean saveState);
496

497
        /**
498
         * When this component has been activated.
499
         * @param state The block state.
500
         * @param world The world.
501
         * @param blockPos The position.
502
         * @param player The player.
503
         * @param hand The hand.
504
         * @param hit The ray trace result.
505
         * @return Action result.
506
         */
507
        public InteractionResult onBlockActivated(BlockState state, Level world, BlockPos blockPos, Player player,
508
                                                 InteractionHand hand, BlockRayTraceResultComponent hit);
509

510
        /**
511
         * @return The direction this component points at.
512
         */
513
        @Nullable
514
        public Direction getRaytraceDirection();
515

516
        /**
517
         * @return If this component should only be raytraced if no other components matched for this face.
518
         */
519
        public boolean isRaytraceLastForFace();
520

521
    }
522

523
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc