Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions common/src/main/java/com/xtracr/realcamera/RealCameraCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class RealCameraCore {
private static Vec3 cameraPos = Vec3.ZERO, eulerAngle = Vec3.ZERO;
private static boolean active = false, rendering = false;
private static int failureFrames = 0;
private static final java.util.Map<BindTarget, java.util.Map<String, DisableConfig[]>> DISABLE_CONFIG_CACHE = new java.util.HashMap<>();

public static boolean isActive() {
return active;
Expand Down Expand Up @@ -108,6 +109,11 @@ public static void computeCamera(Minecraft client, float deltaTick) {
eulerAngle = MathUtil.getEulerAngleYXZ(SmoothUtil.smoothRotation(lastResult.getRotation())).scale(Math.toDegrees(1));
}

private static DisableConfig[] disableConfigsFor(BindTarget target, String textureId) {
return DISABLE_CONFIG_CACHE.computeIfAbsent(target, unused -> new java.util.HashMap<>())
.computeIfAbsent(textureId, key -> target.filteredDisableConfigs(config -> key.contains(config.textureId())));
}

public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBufferSource bufferSource, Matrix4f modelView) {
Vec3 targetEulerAngle = MathUtil.getEulerAngleYXZ(lastResult.getRotation());
Matrix4f invertedCameraPose = new Matrix4f()
Expand All @@ -126,7 +132,7 @@ public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBu
final float m02 = modelView.m02(), m12 = modelView.m12(), m22 = modelView.m22(), m32 = modelView.m32();
final float depth = currentTarget().disablingDepth();
catcher.endCatching(builtBuffer -> {
DisableConfig[] disableConfigs = currentTarget().filteredDisableConfigs(config -> builtBuffer.textureId().contains(config.textureId()));
DisableConfig[] disableConfigs = disableConfigsFor(currentTarget(), builtBuffer.textureId());
for (DisableConfig config : disableConfigs) {
if (config.disableAll()) return;
}
Expand All @@ -135,7 +141,7 @@ public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBu
for (VertexData vertex : builtBuffer.vertexBuffer()) vertex.render(buffer);
return;
}
builtBuffer.vertexBuffer().primitiveStream().forEach(primitive -> {
builtBuffer.vertexBuffer().forEachPrimitive(primitive -> {
primitiveFor:
for (VertexData vertex : primitive) {
if (Math.fma(m02, vertex.x(), Math.fma(m12, vertex.y(), Math.fma(m22, vertex.z(), m32))) > -depth) continue;
Expand All @@ -156,16 +162,13 @@ private static void computeBindResult(BuiltIterableBuffer builtBuffer) {
BindResult result = new BindResult(target, false);
BindTarget.TargetConfig config = target.targetConfig();
VertexData.UV[] uvs = {new VertexData.UV(config.posU(), config.posV()), new VertexData.UV(config.forwardU(), config.forwardV()), new VertexData.UV(config.upwardU(), config.upwardV())};
VertexData[][] primitives = builtBuffer.findPrimitivesInCache(uvs);
if (builtBuffer.anyNotCached(uvs)) {
VertexData[][] primitives = new VertexData[uvs.length][];
int unresolved = builtBuffer.findPrimitivesInCache(uvs, primitives);
if (unresolved != 0) {
for (int i = 0; i < primitives.length; i++) {
if (primitives[i] != null) uvs[i] = null;
}
VertexData[][] newPrimitives = builtBuffer.findPrimitives(uvs);
for (int i = 0; i < primitives.length; i++) {
if (newPrimitives[i] == null && primitives[i] == null) continue targetFor;
else if (newPrimitives[i] != null) primitives[i] = newPrimitives[i];
}
if (builtBuffer.findPrimitives(uvs, primitives, unresolved) != 0) continue;
}
if (primitives[0] != null) result.setPosition(VertexData.position(primitives[0], config.posU(), config.posV()));
if (primitives[1] != null) result.setForward(VertexData.normal(primitives[1]));
Expand Down
58 changes: 47 additions & 11 deletions common/src/main/java/com/xtracr/realcamera/config/BindTarget.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.xtracr.realcamera.util.VertexData;
import it.unimi.dsi.fastutil.floats.Float2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.floats.FloatOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.util.Mth;

Expand Down Expand Up @@ -74,7 +73,18 @@ public boolean isEmpty() {
}

public DisableConfig[] filteredDisableConfigs(Predicate<DisableConfig> filter) {
return Arrays.stream(disableConfigs).filter(filter).toArray(DisableConfig[]::new);
int count = 0;
for (DisableConfig disableConfig : disableConfigs) {
if (filter.test(disableConfig)) count++;
}
if (count == 0) return new DisableConfig[0];
if (count == disableConfigs.length) return disableConfigs;
DisableConfig[] filtered = new DisableConfig[count];
for (int i = 0, j = 0; i < disableConfigs.length; i++) {
DisableConfig disableConfig = disableConfigs[i];
if (filter.test(disableConfig)) filtered[j++] = disableConfig;
}
return filtered;
}

public void write(FriendlyByteBuf byteBuf) {
Expand Down Expand Up @@ -210,18 +220,23 @@ public void write(FriendlyByteBuf byteBuf) {

@JsonAdapter(DisableConfig.Adapter.class)
public static class DisableConfig {
private final Float2ObjectOpenHashMap<FloatOpenHashSet> disableCacheMap = new Float2ObjectOpenHashMap<>();
private static final byte UNKNOWN = 0, DISABLED = 1, ENABLED = 2;
private static final int RECENT_CACHE_MASK = 15;
private final Long2ByteOpenHashMap disableCacheMap;
private final String name;
private final String textureId;
private final boolean disableAll;
private final UVRectangle[] rectangles;
private final long[] recentUvKeys = new long[RECENT_CACHE_MASK + 1];
private final byte[] recentStates = new byte[RECENT_CACHE_MASK + 1];

public DisableConfig(String name, String textureId, boolean disableAll, UVRectangle[] rectangles) {
this.name = name;
this.textureId = textureId;
this.disableAll = disableAll;
this.rectangles = rectangles;
disableCacheMap.defaultReturnValue(FloatOpenHashSet.of());
disableCacheMap = new Long2ByteOpenHashMap(Math.max(256, rectangles.length * 8), 0.5f);
disableCacheMap.defaultReturnValue(UNKNOWN);
}

public static DisableConfig read(FriendlyByteBuf byteBuf) {
Expand Down Expand Up @@ -263,20 +278,41 @@ public void write(FriendlyByteBuf byteBuf) {

public boolean disable(VertexData vertex) {
final float u = vertex.u(), v = vertex.v();
final FloatOpenHashSet cachedVs = disableCacheMap.get(u);
if (!cachedVs.isEmpty()) {
if (cachedVs.contains(v)) return true;
if (cachedVs.contains(-v)) return false;
final long uv = uvKey(u, v);
final int slot = recentSlot(uv);
final byte recentState = recentStates[slot];
if (recentState != UNKNOWN && recentUvKeys[slot] == uv) return recentState == DISABLED;
final byte cached = disableCacheMap.get(uv);
if (cached != UNKNOWN) {
rememberRecent(uv, cached);
return cached == DISABLED;
}
for (UVRectangle rect : rectangles) {
if (!rect.contains(u, v)) continue;
disableCacheMap.computeIfAbsent(u, k -> new FloatOpenHashSet()).add(v);
disableCacheMap.put(uv, DISABLED);
rememberRecent(uv, DISABLED);
return true;
}
disableCacheMap.computeIfAbsent(u, k -> new FloatOpenHashSet()).add(-v);
disableCacheMap.put(uv, ENABLED);
rememberRecent(uv, ENABLED);
return false;
}

private static long uvKey(float u, float v) {
return (long) Float.floatToRawIntBits(u) << 32 | (Float.floatToRawIntBits(v) & 0xffffffffL);
}

private static int recentSlot(long uv) {
long mixed = uv ^ (uv >>> 33) ^ (uv >>> 17);
return (int) mixed & RECENT_CACHE_MASK;
}

private void rememberRecent(long uv, byte state) {
int slot = recentSlot(uv);
recentUvKeys[slot] = uv;
recentStates[slot] = state;
}

public static class Adapter extends TypeAdapter<DisableConfig> {
@Override
public void write(JsonWriter out, DisableConfig value) throws IOException {
Expand Down
186 changes: 130 additions & 56 deletions common/src/main/java/com/xtracr/realcamera/util/BuiltIterableBuffer.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.xtracr.realcamera.util;


import com.mojang.blaze3d.vertex.MeshData;
import com.xtracr.realcamera.util.VertexData.UV;
import net.minecraft.client.renderer.RenderType;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
Expand All @@ -15,7 +13,9 @@
public record BuiltIterableBuffer(RenderType renderType, String textureId, IterableVertexBuffer vertexBuffer) {
private static final Pattern TEXTURE_ID_PATTERN = Pattern.compile("texture\\[Optional\\[(.*?)]");
private static final Map<RenderType, String> TEXTURE_ID_CACHE = new HashMap<>();
private static final Map<RenderType, Map<UV, float[]>> FIND_PRIMITIVE_CACHE = new HashMap<>();
private static final Map<RenderType, Map<UV, PrimitiveCache>> FIND_PRIMITIVE_CACHE = new HashMap<>();

private record PrimitiveCache(int index, float[] uvCache) { }

public static BuiltIterableBuffer buildFrom(RenderType renderType, MeshData meshData) {
String textureId = TEXTURE_ID_CACHE.computeIfAbsent(renderType, rt -> {
Expand All @@ -27,77 +27,151 @@ public static BuiltIterableBuffer buildFrom(RenderType renderType, MeshData mesh
}

public boolean anyNotCached(UV[] uvs) {
Map<UV, float[]> cache = FIND_PRIMITIVE_CACHE.get(renderType);
Map<UV, PrimitiveCache> cache = FIND_PRIMITIVE_CACHE.get(renderType);
if (cache == null) return true;
for (UV uv : uvs) {
if (!cache.containsKey(uv)) return true;
if (uv != null && !cache.containsKey(uv)) return true;
}
return false;
}

public VertexData[] @Nullable [] findPrimitivesInCache(UV[] uvs) {
Map<UV, float[]> cache = FIND_PRIMITIVE_CACHE.get(renderType);
int length = renderType.mode().primitiveLength, uvsLength = uvs.length;
VertexData[][] primitives = new VertexData[uvsLength][];
if (cache == null) return primitives;
float[][] uvCacheArray = new float[uvsLength][];
boolean allNull = true;
for (int i = 0; i < uvsLength; i++) {
uvCacheArray[i] = cache.get(uvs[i]);
if (uvCacheArray[i] != null) allNull = false;
VertexData[][] primitives = new VertexData[uvs.length][];
findPrimitivesInCache(uvs, primitives);
return primitives;
}

public int findPrimitivesInCache(UV[] uvs, VertexData[] @Nullable [] primitives) {
Map<UV, PrimitiveCache> cache = FIND_PRIMITIVE_CACHE.get(renderType);
int unresolved = 0;
if (cache == null) {
for (UV uv : uvs) {
if (uv != null) unresolved++;
}
return unresolved;
}
if (allNull) return primitives;
vertexBuffer.primitiveStream().anyMatch(primitive -> {
float[] uvCache;
boolean allFound = true;
cacheFor:
for (int i = 0; i < uvsLength; i++) {
if (primitives[i] != null) continue;
uvCache = uvCacheArray[i];
if (uvCache == null) continue;
for (int j = 0; j < length; j++) {
if (uvCache[j * 2] != primitive[j].u() || uvCache[j * 2 + 1] != primitive[j].v()) {
allFound = false;
continue cacheFor;
}
int primitiveCount = vertexBuffer.primitiveCount();
int cachedIndex0 = -1, cachedIndex1 = -1, cachedIndex2 = -1;
VertexData[] primitive0 = null, primitive1 = null, primitive2 = null;
for (int i = 0; i < uvs.length; i++) {
UV uv = uvs[i];
if (uv == null) continue;
PrimitiveCache cachedPrimitive = cache.get(uv);
if (cachedPrimitive == null) {
unresolved++;
continue;
}
int primitiveIndex = cachedPrimitive.index();
if (primitiveIndex >= primitiveCount) {
cache.remove(uv);
unresolved++;
continue;
}
if (!vertexBuffer.primitiveMatchesUVs(primitiveIndex, cachedPrimitive.uvCache())) {
cache.remove(uv);
unresolved++;
continue;
}
VertexData[] primitive;
if (primitiveIndex == cachedIndex0) primitive = primitive0;
else if (primitiveIndex == cachedIndex1) primitive = primitive1;
else if (primitiveIndex == cachedIndex2) primitive = primitive2;
else {
primitive = vertexBuffer.readPrimitiveAt(primitiveIndex);
if (cachedIndex0 == -1) {
cachedIndex0 = primitiveIndex;
primitive0 = primitive;
} else if (cachedIndex1 == -1) {
cachedIndex1 = primitiveIndex;
primitive1 = primitive;
} else {
cachedIndex2 = primitiveIndex;
primitive2 = primitive;
}
primitives[i] = VertexData.asImmutable(primitive);
}
return allFound;
});
return primitives;
primitives[i] = primitive;
}
return unresolved;
}

public VertexData[] @Nullable [] findPrimitives(UV[] uvs) {
final int resolution = 1000000;
int length = renderType.mode().primitiveLength, uvsLength = uvs.length;
int[] us = new int[length], vs = new int[length];
VertexData[][] primitives = new VertexData[uvsLength][];
vertexBuffer.primitiveStream().anyMatch(primitive -> {
for (int i = 0; i < length; i++) {
us[i] = (int) (resolution * primitive[i].u());
vs[i] = (int) (resolution * primitive[i].v());
UV[] unresolvedUvs = uvs.clone();
VertexData[][] primitives = new VertexData[unresolvedUvs.length][];
int unresolved = 0;
for (UV uv : unresolvedUvs) {
if (uv != null) unresolved++;
}
findPrimitives(unresolvedUvs, primitives, unresolved);
return primitives;
}

public int findPrimitives(UV[] uvs, VertexData[] @Nullable [] primitives, int unresolved) {
int length = renderType.mode().primitiveLength;
float[] primitiveUvs = new float[length * 2];
Map<UV, PrimitiveCache> cache = FIND_PRIMITIVE_CACHE.computeIfAbsent(renderType, k -> new HashMap<>());
for (int primitiveIndex = 0, primitiveCount = vertexBuffer.primitiveCount(); primitiveIndex < primitiveCount && unresolved > 0; primitiveIndex++) {
vertexBuffer.readPrimitiveUVs(primitiveIndex, primitiveUvs);
float minU = primitiveUvs[0], maxU = primitiveUvs[0], minV = primitiveUvs[1], maxV = primitiveUvs[1];
for (int i = 1; i < length; i++) {
float primitiveU = primitiveUvs[i * 2], primitiveV = primitiveUvs[i * 2 + 1];
minU = Math.min(minU, primitiveU);
maxU = Math.max(maxU, primitiveU);
minV = Math.min(minV, primitiveV);
maxV = Math.max(maxV, primitiveV);
}
Polygon polygon = new Polygon(us, vs, length);
boolean allFound = true;
for (int i = 0; i < uvsLength; i++) {
if (primitives[i] != null) continue;
VertexData[] primitive = null;
float[] uvCache = null;
for (int i = 0; i < uvs.length; i++) {
UV uv = uvs[i];
if (uv == null) continue;
if (!polygon.contains(resolution * uv.u(), resolution * uv.v())) {
allFound = false;
double u = uv.u(), v = uv.v();
if (u < minU || u > maxU || v < minV || v > maxV || !primitiveContains(primitiveUvs, length, u, v)) {
continue;
}
float[] uvCache = new float[length * 2];
for (int j = 0; j < length; j++) {
uvCache[j * 2] = primitive[j].u();
uvCache[j * 2 + 1] = primitive[j].v();
if (primitive == null) {
primitive = vertexBuffer.readPrimitiveAt(primitiveIndex);
}
if (uvCache == null) {
uvCache = primitiveUvs.clone();
}
FIND_PRIMITIVE_CACHE.computeIfAbsent(renderType, k -> new HashMap<>()).put(uv, uvCache);
primitives[i] = VertexData.asImmutable(primitive);
cache.put(uv, new PrimitiveCache(primitiveIndex, uvCache));
primitives[i] = primitive;
uvs[i] = null;
unresolved--;
}
return allFound;
});
return primitives;
}
return unresolved;
}

private static boolean primitiveContains(float[] primitiveUvs, int length, double u, double v) {
if (length == 0) return false;
if (length == 1) {
return primitiveUvs[0] == u && primitiveUvs[1] == v;
}
if (length == 2) {
return pointOnSegment(primitiveUvs[0], primitiveUvs[1], primitiveUvs[2], primitiveUvs[3], u, v);
}
boolean inside = false;
for (int i = 0, j = length - 1; i < length; j = i++) {
int current = i * 2;
int previous = j * 2;
double currentU = primitiveUvs[current], currentV = primitiveUvs[current + 1];
double previousU = primitiveUvs[previous], previousV = primitiveUvs[previous + 1];
if (pointOnSegment(currentU, currentV, previousU, previousV, u, v)) {
return true;
}
boolean intersects = ((currentV > v) != (previousV > v)) &&
(u < (previousU - currentU) * (v - currentV) / (previousV - currentV) + currentU);
if (intersects) {
inside = !inside;
}
}
return inside;
}

private static boolean pointOnSegment(double u1, double v1, double u2, double v2, double u, double v) {
double cross = (u2 - u1) * (v - v1) - (v2 - v1) * (u - u1);
if (Math.abs(cross) > 1.0E-7D) return false;
return u >= Math.min(u1, u2) && u <= Math.max(u1, u2) && v >= Math.min(v1, v2) && v <= Math.max(v1, v2);
}
}
}
Loading