Skip to content

Commit 98176b5

Browse files
committed
Improve adjacent sprite matching
- Collect all sprites on a face instead of only the first one - Always use the light face instead of the cull face to decide which sprites are on a face - Resolve #242
1 parent cb9b4a8 commit 98176b5

File tree

3 files changed

+44
-32
lines changed

3 files changed

+44
-32
lines changed

src/main/java/me/pepperbell/continuity/client/processor/overlay/StandardOverlayQuadProcessor.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ public ProcessingResult processQuadInner(MutableQuadView quad, Sprite sprite, Bl
8383
return ProcessingResult.NEXT_PROCESSOR;
8484
}
8585

86+
protected static boolean matchesAny(Set<Identifier> tiles, Set<Sprite> sprites) {
87+
for (Sprite sprite : sprites) {
88+
if (tiles.contains(sprite.getContents().getId())) {
89+
return true;
90+
}
91+
}
92+
return false;
93+
}
94+
8695
protected boolean appliesOverlay(BlockState otherAppearanceState, BlockState otherState, BlockPos otherPos, BlockRenderView blockView, BlockState appearanceState, BlockState state, BlockPos pos, Direction face, Sprite quadSprite) {
8796
// OptiFine never applies overlays from blocks with dynamic bounds. To improve mod compatibility, call
8897
// isFullCube with the correct values and do not check for dynamic bounds explicitly. For vanilla blocks, this
@@ -96,7 +105,7 @@ protected boolean appliesOverlay(BlockState otherAppearanceState, BlockState oth
96105
}
97106
}
98107
if (connectTilesSet != null) {
99-
if (!connectTilesSet.contains(SpriteCalculator.getSprite(otherAppearanceState, face).getContents().getId())) {
108+
if (!matchesAny(connectTilesSet, SpriteCalculator.getSprites(otherAppearanceState, face))) {
100109
return false;
101110
}
102111
}
@@ -113,7 +122,7 @@ protected boolean hasSameOverlay(@Nullable BlockState otherAppearanceState, Dire
113122
}
114123
}
115124
if (matchTilesSet != null) {
116-
if (!matchTilesSet.contains(SpriteCalculator.getSprite(otherAppearanceState, face).getContents().getId())) {
125+
if (!matchesAny(matchTilesSet, SpriteCalculator.getSprites(otherAppearanceState, face))) {
117126
return false;
118127
}
119128
}

src/main/java/me/pepperbell/continuity/client/properties/BasicConnectingCtmProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public boolean shouldConnect(BlockRenderView blockView, BlockState appearanceSta
7777
if (appearanceState == otherAppearanceState) {
7878
return true;
7979
}
80-
return quadSprite == SpriteCalculator.getSprite(otherAppearanceState, face);
80+
return SpriteCalculator.getSprites(otherAppearanceState, face).contains(quadSprite);
8181
}
8282
},
8383
STATE {

src/main/java/me/pepperbell/continuity/client/util/SpriteCalculator.java

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
import java.util.EnumMap;
44
import java.util.List;
5+
import java.util.Set;
56
import java.util.concurrent.locks.StampedLock;
67
import java.util.function.Supplier;
78

8-
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
9+
import org.apache.commons.lang3.ArrayUtils;
10+
import org.jetbrains.annotations.Unmodifiable;
11+
12+
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
13+
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
914
import net.fabricmc.fabric.api.client.rendering.v1.InvalidateRenderStateCallback;
1015
import net.minecraft.block.BlockState;
1116
import net.minecraft.client.MinecraftClient;
@@ -18,6 +23,7 @@
1823

1924
public final class SpriteCalculator {
2025
private static final BlockModels MODELS = MinecraftClient.getInstance().getBakedModelManager().getBlockModels();
26+
private static final Direction[] CULL_FACES = ArrayUtils.add(Direction.values(), null);
2127

2228
private static final EnumMap<Direction, SpriteCache> SPRITE_CACHES = new EnumMap<>(Direction.class);
2329

@@ -29,31 +35,27 @@ public final class SpriteCalculator {
2935
InvalidateRenderStateCallback.EVENT.register(SpriteCalculator::clearCache);
3036
}
3137

32-
public static Sprite getSprite(BlockState state, Direction face) {
33-
return SPRITE_CACHES.get(face).getSprite(state);
38+
@Unmodifiable
39+
public static Set<Sprite> getSprites(BlockState state, Direction face) {
40+
return SPRITE_CACHES.get(face).getSprites(state);
3441
}
3542

36-
public static Sprite calculateSprite(BlockState state, Direction face, Supplier<Random> randomSupplier) {
43+
@Unmodifiable
44+
public static Set<Sprite> calculateSprites(BlockState state, Direction face, Supplier<Random> randomSupplier) {
45+
List<Sprite> sprites = new ReferenceArrayList<>();
3746
BakedModel model = MODELS.getModel(state);
3847
try {
39-
List<BakedQuad> quads = model.getQuads(state, face, randomSupplier.get());
40-
if (!quads.isEmpty()) {
41-
return quads.get(0).getSprite();
42-
}
43-
quads = model.getQuads(state, null, randomSupplier.get());
44-
if (!quads.isEmpty()) {
45-
int amount = quads.size();
46-
for (int i = 0; i < amount; i++) {
47-
BakedQuad quad = quads.get(i);
48+
for (Direction cullFace : CULL_FACES) {
49+
for (BakedQuad quad : model.getQuads(state, cullFace, randomSupplier.get())) {
4850
if (quad.getFace() == face) {
49-
return quad.getSprite();
51+
sprites.add(quad.getSprite());
5052
}
5153
}
5254
}
5355
} catch (Exception e) {
5456
//
5557
}
56-
return model.getParticleSprite();
58+
return !sprites.isEmpty() ? Set.copyOf(sprites) : Set.of(model.getParticleSprite());
5759
}
5860

5961
public static void clearCache() {
@@ -64,7 +66,7 @@ public static void clearCache() {
6466

6567
private static class SpriteCache {
6668
private final Direction face;
67-
private final Reference2ReferenceOpenHashMap<BlockState, Sprite> sprites = new Reference2ReferenceOpenHashMap<>();
69+
private final Reference2ObjectOpenHashMap<BlockState, Set<Sprite>> spritesMap = new Reference2ObjectOpenHashMap<>();
6870
private final Supplier<Random> randomSupplier = new Supplier<>() {
6971
private final Random random = Random.create();
7072

@@ -81,18 +83,19 @@ public SpriteCache(Direction face) {
8183
this.face = face;
8284
}
8385

84-
public Sprite getSprite(BlockState state) {
85-
Sprite sprite;
86+
@Unmodifiable
87+
public Set<Sprite> getSprites(BlockState state) {
88+
Set<Sprite> sprites;
8689

8790
long optimisticReadStamp = lock.tryOptimisticRead();
8891
if (optimisticReadStamp != 0L) {
8992
try {
9093
// This map read could happen at the same time as a map write, so catch any exceptions.
9194
// This is safe due to the map implementation used, which is guaranteed to not mutate the map during
9295
// a read.
93-
sprite = sprites.get(state);
94-
if (sprite != null && lock.validate(optimisticReadStamp)) {
95-
return sprite;
96+
sprites = spritesMap.get(state);
97+
if (sprites != null && lock.validate(optimisticReadStamp)) {
98+
return sprites;
9699
}
97100
} catch (Exception e) {
98101
//
@@ -101,31 +104,31 @@ public Sprite getSprite(BlockState state) {
101104

102105
long readStamp = lock.readLock();
103106
try {
104-
sprite = sprites.get(state);
107+
sprites = spritesMap.get(state);
105108
} finally {
106109
lock.unlockRead(readStamp);
107110
}
108111

109-
if (sprite == null) {
112+
if (sprites == null) {
110113
long writeStamp = lock.writeLock();
111114
try {
112-
sprite = sprites.get(state);
113-
if (sprite == null) {
114-
sprite = calculateSprite(state, face, randomSupplier);
115-
sprites.put(state, sprite);
115+
sprites = spritesMap.get(state);
116+
if (sprites == null) {
117+
sprites = calculateSprites(state, face, randomSupplier);
118+
spritesMap.put(state, sprites);
116119
}
117120
} finally {
118121
lock.unlockWrite(writeStamp);
119122
}
120123
}
121124

122-
return sprite;
125+
return sprites;
123126
}
124127

125128
public void clear() {
126129
long writeStamp = lock.writeLock();
127130
try {
128-
sprites.clear();
131+
spritesMap.clear();
129132
} finally {
130133
lock.unlockWrite(writeStamp);
131134
}

0 commit comments

Comments
 (0)