Skip to content

Commit f3faf58

Browse files
j-shelfwoodclaude
andcommitted
feat: Add world curvature effect (DH-inspired)
Implements spherical world curvature for LOD terrain, simulating standing on a planetary surface. Distant terrain curves downward based on configurable curvature ratio. - Add earthCurveRatio config (0=disabled, 50-500 valid range) - Add uEarthRadius uniform to shader SceneUniform - Implement applyWorldCurvature() in quad_util.glsl - Add "World Curvature" slider to Sodium Video Settings - Sync config between NeoForge config system and VoxyConfig Math based on Distant Horizons' curve.vert implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e64646c commit f3faf58

File tree

7 files changed

+81
-1
lines changed

7 files changed

+81
-1
lines changed

src/main/java/me/cortex/voxy/client/config/VoxyConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ public class VoxyConfig {
3737
// Range: 0-4 blocks, default 1 (original Voxy behavior)
3838
public int lodBoundaryBuffer = 1;
3939

40+
// World curvature: simulates standing on a spherical planet
41+
// 0 = disabled (flat world)
42+
// 1 = real Earth curvature (6371km radius)
43+
// Higher values = more extreme curvature (smaller planet effect)
44+
// Range: 0, or 50-5000 (values 1-49 are invalid and auto-corrected to 50)
45+
// Inspired by Distant Horizons' earth curvature feature
46+
public int earthCurveRatio = 0;
47+
4048
private static VoxyConfig loadOrCreate() {
4149
if (VoxyCommon.isAvailable()) {
4250
var path = getConfigPath();

src/main/java/me/cortex/voxy/client/config/VoxyNeoForgeConfig.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ public class VoxyNeoForgeConfig {
6767
"0 = exact match (may have gaps), 1 = minimal overlap, 2-4 = smoother for fast flight")
6868
.defineInRange("lodBoundaryBuffer", 1, 0, 4);
6969

70+
// World curvature (experimental)
71+
private static final ModConfigSpec.IntValue EARTH_CURVE_RATIO = BUILDER
72+
.comment("World curvature effect - simulates standing on a spherical planet",
73+
"0 = disabled (flat world)",
74+
"1 = real Earth curvature (6371km radius)",
75+
"Higher values = more extreme curvature (smaller planet effect)",
76+
"Valid range: 0 (off), or 50-5000. Values 1-49 are auto-corrected to 50.",
77+
"Inspired by Distant Horizons' earth curvature feature")
78+
.defineInRange("earthCurveRatio", 0, 0, 5000);
79+
7080
// Debug settings
7181
private static final ModConfigSpec.BooleanValue RENDER_STATISTICS = BUILDER
7282
.comment("Show render statistics in F3 debug screen",
@@ -96,6 +106,7 @@ private static void syncToVoxyConfig() {
96106
VoxyConfig.CONFIG.useEnvironmentalFog = USE_ENVIRONMENTAL_FOG.get();
97107
VoxyConfig.CONFIG.dontUseSodiumBuilderThreads = DONT_USE_SODIUM_BUILDER_THREADS.get();
98108
VoxyConfig.CONFIG.lodBoundaryBuffer = LOD_BOUNDARY_BUFFER.get();
109+
VoxyConfig.CONFIG.earthCurveRatio = EARTH_CURVE_RATIO.get();
99110

100111
// RenderStatistics is a runtime-only setting (not saved to JSON)
101112
RenderStatistics.enabled = RENDER_STATISTICS.get();
@@ -158,4 +169,8 @@ public static int getLodBoundaryBuffer() {
158169
public static boolean isRenderStatisticsEnabled() {
159170
return RENDER_STATISTICS.get();
160171
}
172+
173+
public static int getEarthCurveRatio() {
174+
return EARTH_CURVE_RATIO.get();
175+
}
161176
}

src/main/java/me/cortex/voxy/client/config/VoxySodiumOptions.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ private static OptionGroup createAdvancedGroup(VoxyConfigStorage storage) {
150150
config -> RenderStatistics.enabled
151151
)
152152
.build())
153+
.add(OptionImpl.createBuilder(int.class, storage)
154+
.setName(Component.translatable("voxy.sodium.option.earth_curve_ratio"))
155+
.setTooltip(Component.translatable("voxy.sodium.option.earth_curve_ratio.tooltip"))
156+
.setControl(opt -> new SliderControl(opt, 0, 500, 10,
157+
v -> v == 0 ? Component.literal("Disabled") :
158+
v < 50 ? Component.literal("→ 50 (min)") :
159+
Component.literal(v + "x")))
160+
.setBinding(
161+
(config, value) -> config.earthCurveRatio = value < 50 && value > 0 ? 50 : value,
162+
config -> config.earthCurveRatio
163+
)
164+
.build())
153165
.build();
154166
}
155167

src/main/java/me/cortex/voxy/client/core/rendering/section/backend/mdic/MDICSectionRenderer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
1818
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
1919
import me.cortex.voxy.client.core.rendering.util.UploadStream;
20+
import me.cortex.voxy.client.config.VoxyConfig;
2021
import me.cortex.voxy.common.Logger;
2122
import me.cortex.voxy.common.world.WorldEngine;
2223
import net.minecraft.client.Minecraft;
@@ -146,6 +147,16 @@ private void uploadUniformBuffer(MDICViewport viewport) {
146147
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
147148
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
148149

150+
// Earth curvature radius: 0 = disabled, otherwise compute radius in blocks
151+
// DH uses: radius = 6371000.0 / ratio (Earth radius in meters / ratio factor)
152+
// We use blocks (1 block = 1 meter), so same formula
153+
int earthCurveRatio = VoxyConfig.CONFIG.earthCurveRatio;
154+
float earthRadius = 0.0f;
155+
if (earthCurveRatio >= 50) {
156+
earthRadius = 6371000.0f / earthCurveRatio;
157+
}
158+
MemoryUtil.memPutFloat(ptr, earthRadius); ptr += 4;
159+
149160
UploadStream.INSTANCE.commit();
150161
}
151162

src/main/resources/assets/voxy/lang/en_us.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,8 @@
6464
"voxy.sodium.option.lod_boundary_buffer.tooltip": "Controls overlap between vanilla chunks and LODs. Higher = smoother transitions when flying fast, but slight overdraw.",
6565

6666
"voxy.sodium.option.render_statistics": "Debug Statistics",
67-
"voxy.sodium.option.render_statistics.tooltip": "Show LOD render statistics in the F3 debug screen"
67+
"voxy.sodium.option.render_statistics.tooltip": "Show LOD render statistics in the F3 debug screen",
68+
69+
"voxy.sodium.option.earth_curve_ratio": "World Curvature",
70+
"voxy.sodium.option.earth_curve_ratio.tooltip": "Simulates standing on a spherical planet. 0 = flat world. Higher values = smaller planet effect (more curvature). Inspired by Distant Horizons."
6871
}

src/main/resources/assets/voxy/shaders/lod/gl46/bindings.glsl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ layout(binding = 0, std140) uniform SceneUniform {
33
ivec3 baseSectionPos;
44
uint frameId;
55
vec3 cameraSubPos;
6+
float uEarthRadius; // 0.0 = disabled, otherwise radius in blocks for world curvature
67
};
78

89
//TODO: see if making the stride 2*4*4 bytes or something cause you get that 16 byte write

src/main/resources/assets/voxy/shaders/lod/quad_util.glsl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,39 @@ void setupQuad(out QuadData quad, const in Quad rawQuad, uvec2 sPos, bool genera
162162
quad.uvCorner = faceSize.xz;
163163
}
164164

165+
// Apply spherical world curvature effect (inspired by Distant Horizons)
166+
// Makes distant terrain curve downward as if standing on a spherical planet
167+
vec3 applyWorldCurvature(vec3 worldPos) {
168+
if (uEarthRadius <= 0.0) {
169+
return worldPos;
170+
}
171+
172+
// Calculate local radius at vertex height
173+
float localRadius = uEarthRadius + worldPos.y;
174+
175+
// Calculate the arc angle based on horizontal distance
176+
float horizontalDist = length(worldPos.xz);
177+
float phi = horizontalDist / localRadius;
178+
179+
// Apply spherical projection
180+
// Y displacement: terrain curves down at distance
181+
worldPos.y += (cos(phi) - 1.0) * localRadius;
182+
183+
// XZ scaling: slight convergence at extreme distances (prevents horizon stretching)
184+
if (phi > 0.0001) {
185+
worldPos.xz = worldPos.xz * sin(phi) / phi;
186+
}
187+
188+
return worldPos;
189+
}
190+
165191
vec4 getQuadCornerPos(in QuadData quad, uint cornerId) {
166192
vec2 cornerMask = vec2((cornerId>>1)&1u, cornerId&1u)*quad.lodScale;
167193
vec3 point = quad.basePoint + swizzelDataAxis(quad.axis,vec3(quad.quadSizeAddin*cornerMask,0));
194+
195+
// Apply world curvature before MVP transformation
196+
point = applyWorldCurvature(point);
197+
168198
vec4 pos = MVP * vec4(point, 1.0f);
169199
pos.xy += taaOffset*pos.w;
170200
return pos;

0 commit comments

Comments
 (0)