Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
a50c842
Initial testing
Peregrine05 Nov 17, 2023
ea4b35d
Remove Point3, Point3i, and Tuple3
Peregrine05 Nov 18, 2023
422d7e2
Just save changes
Peregrine05 Jun 1, 2024
e39d23b
Save changes
Peregrine05 Jun 1, 2024
5a949f7
Save changes
Peregrine05 Jun 5, 2024
07ebdd6
Save changes
Peregrine05 Sep 4, 2024
ca739fe
Save Changes
Peregrine05 Sep 4, 2024
574bfdb
Fix IllegalArgumentException
Peregrine05 Sep 4, 2024
e652b8b
Sample sky, fix bamboo, camera JSON
Peregrine05 Sep 5, 2024
7c3dd20
Code cleanup
Peregrine05 Sep 5, 2024
17e4233
Revert clouds changes
Peregrine05 Sep 30, 2024
80ca921
Maybe better scattering
Peregrine05 Oct 17, 2024
8e3a777
Fix blackness on BVH intersections
Peregrine05 Oct 17, 2024
bd0ca90
Save changes
Peregrine05 Aug 27, 2025
71bf1b8
Add label to emitterMappingType
Peregrine05 Aug 27, 2025
8f44acc
Move controls and remove unused controls
Peregrine05 Aug 27, 2025
616ee90
Disable branched path tracing
Peregrine05 Aug 27, 2025
8eb0ebb
Remove mistakenly added files
Peregrine05 Aug 28, 2025
fae3326
Fix file permissions
NotStirred Aug 28, 2025
66374ce
Merge pull request #19 from NotStirred/fix/file_perms
Peregrine05 Aug 28, 2025
210af3a
Rename Ray2 to Ray
Peregrine05 Aug 28, 2025
448f51e
Fix cloud layer GUI
Peregrine05 Aug 28, 2025
f53cf15
Remove commented-out code
Peregrine05 Aug 29, 2025
3da6dbd
Hit water plane if it is enabled
Peregrine05 Aug 29, 2025
1f45521
Hit water plane with cauldron model
Peregrine05 Aug 29, 2025
c6a87c1
Improve tintColor
Peregrine05 Aug 29, 2025
fb8ea8d
Fix incorrect normal with water
Peregrine05 Aug 29, 2025
d4af62b
Update PluginApiTest
Peregrine05 Aug 29, 2025
6595366
Undo unnecessary changes
Peregrine05 Aug 29, 2025
186095c
Move path tracer functionality to methods
Peregrine05 Sep 3, 2025
151244c
Fix chunks of void in octree loaded chunks
Peregrine05 Sep 3, 2025
6c4a8e6
Slight code simplification
Peregrine05 Sep 3, 2025
5298d7e
Improve render preview tooltip information
Peregrine05 Sep 3, 2025
86f73cf
Interface to flags for Ray and IntersectionRecord
Peregrine05 Sep 3, 2025
7bcd191
Revert unnecessary changes
Peregrine05 Sep 3, 2025
85a0a81
Remove magic boolean in Leaves
Peregrine05 Sep 3, 2025
da435d1
Give CopperGrate its own class
Peregrine05 Sep 3, 2025
7ed87c5
Revert changes to entities, in favor of #1376
Peregrine05 Sep 3, 2025
e67112c
Undo changes to ApertureShape in favor of #1835
Peregrine05 Sep 3, 2025
bbe3b08
Remove duplicate code
Peregrine05 Sep 5, 2025
64317d8
Remove commented code
Peregrine05 Sep 5, 2025
1df659a
Revert changes to clouds
Peregrine05 Sep 5, 2025
7cd1cdc
`onEdge` only needs the distance.
Peregrine05 Sep 5, 2025
6e12575
undo spelling change
Peregrine05 Sep 6, 2025
44d9536
no newline at end of file
Peregrine05 Sep 6, 2025
72ca976
Add comments and Javadoc
Peregrine05 Sep 7, 2025
8b10d53
Fix issue with intersection
Peregrine05 Sep 8, 2025
1031807
Fix intersection issues with glass panes
Peregrine05 Sep 8, 2025
2db9936
Fix issue with ESS
Peregrine05 Sep 9, 2025
0d3b541
Simplify code slightly
Peregrine05 Sep 9, 2025
43ad930
Move `skyOcclusion` to `Scene`.
Peregrine05 Sep 10, 2025
b9ae5a9
Fix subsurface scattering
Peregrine05 Sep 11, 2025
549aa01
Add `Remove all entities` control
Peregrine05 Sep 11, 2025
9884339
Remove unused constants
Peregrine05 Sep 11, 2025
2177fd3
Fix sky emittance JSON
Peregrine05 Sep 11, 2025
5e8a954
Remove unused constant
Peregrine05 Sep 11, 2025
a2a1275
Remove commented out code
Peregrine05 Sep 12, 2025
7e8cd85
Fix removing non-actors not rebuilding the non-actor BVH
Peregrine05 Sep 12, 2025
baef6f8
Implement AgX tonemapping and Vignette filter
Peregrine05 Sep 12, 2025
d51caf9
Remove output clamping from AgX tonemapping
Peregrine05 Sep 12, 2025
f406187
Merge branch 'master' into new-pt
Peregrine05 Sep 13, 2025
8c19819
Update ColorUtilTest.java
Peregrine05 Sep 13, 2025
b35a9b8
Update copyright date and add credit to ControlsFX
Peregrine05 Sep 13, 2025
0cd442f
Fix Materials tab controls not updating
Peregrine05 Sep 14, 2025
e693825
Use correct block ID for `resin_brick_wall`
Peregrine05 Sep 14, 2025
19bf279
Parse canvasConfig width and height if present
Peregrine05 Sep 14, 2025
f9d6c4f
Actually save cloudLayers to JSON
Peregrine05 Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ For guides and more information please checkout the [Documentation][chunky-dev].
<details>
<summary><strong>Which Minecraft versions are supported?</strong></summary>

> Chunky 2.4.4 supports Minecraft 1.2-1.19.2 worlds and Cubic Chunks for Minecraft 1.10-1.12 worlds.
> Chunky 2.5.0 supports Minecraft 1.2-1.21.8 worlds and Cubic Chunks for Minecraft 1.10-1.12 worlds.
>
> We typically add new blocks shortly after a new Minecraft snapshot is released. Use the latest Chunky snapshot to render them until a new Chunky version is released.

Expand Down Expand Up @@ -189,7 +189,7 @@ when in doubt.

## Copyright & License

Chunky is Copyright (c) 2010-2023, Jesper Öqvist <[email protected]> and [Chunky Contributors][chunky-contributors].
Chunky is Copyright (c) 2010-2025, Jesper Öqvist <[email protected]> and [Chunky Contributors][chunky-contributors].

Permission to modify and redistribute is granted under the terms of
the GPLv3 license. See the file `LICENSE` for the full license.
Expand Down Expand Up @@ -221,6 +221,8 @@ Chunky uses the following 3rd party libraries:
See the file `licenses/Apache-2.0.txt` for the full license text.
See the file `licenses/lz4-java.txt` for the copyright notice.
lz4-java uses LZ4, by Yann Collet, which is covered by the BSD 2-Clause license. See the file `licenses/lz4.txt` for the copyright notice and full license.
- **ControlsFX by the ControlsFX Project team**
The library is released under the BSD 3-Clause license. See the file `licenses/controlsfx.txt` for the copyright notice and license text.

## Special Thanks

Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ subprojects {
languageVersion = JavaLanguageVersion.of(21)
}
useJUnitPlatform()

// Always run tests, even when nothing changed.
dependsOn 'cleanTest'

Expand Down
3 changes: 2 additions & 1 deletion chunky/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
implementation 'it.unimi.dsi:fastutil:8.4.4'
implementation 'org.apache.commons:commons-math3:3.2'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.controlsfx:controlsfx:11.2.1'
implementation 'org.lz4:lz4-java:1.8.0'
implementation 'org.apache.maven:maven-artifact:3.9.9'
implementation project(':lib')
Expand All @@ -31,7 +32,7 @@ dependencies {
javafx {
version = '17.0.11'
configuration = 'implementation'
modules = ['javafx.base', 'javafx.controls', 'javafx.fxml']
modules = ['javafx.base', 'javafx.controls', 'javafx.fxml', 'javafx.media']
}

jar {
Expand Down
188 changes: 184 additions & 4 deletions chunky/src/java/se/llbit/chunky/block/AbstractModelBlock.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package se.llbit.chunky.block;

import se.llbit.chunky.block.minecraft.Water;
import se.llbit.chunky.model.BlockModel;
import se.llbit.chunky.model.minecraft.WaterModel;
import se.llbit.chunky.plugin.PluginApi;
import se.llbit.chunky.renderer.scene.Scene;
import se.llbit.chunky.resources.Texture;
import se.llbit.math.Ray;
import se.llbit.math.Vector3;
import se.llbit.math.*;

import java.util.Random;

Expand Down Expand Up @@ -41,9 +42,188 @@ public BlockModel getModel() {
return model;
}

/**
* Moves {@code o} to the intersection point (specified by {@code d} and {@code distance}),
* and checks if it is at the edge of the block.
* @param o Origin point (which is updated)
* @param d Ray direction
* @param distance Distance to intersection
* @return Whether the intersection point is on the edge of the block.
*/
public static boolean onEdge(Vector3 o, Vector3 d, double distance) {
o.scaleAdd(distance, d);
double ix = o.x - QuickMath.floor(o.x);
double iy = o.y - QuickMath.floor(o.y);
double iz = o.z - QuickMath.floor(o.z);
return !(Constants.EPSILON < ix && ix < 1 - Constants.EPSILON &&
Constants.EPSILON < iy && iy < 1 - Constants.EPSILON &&
Constants.EPSILON < iz && iz < 1 - Constants.EPSILON);
}

@Override
public boolean intersect(Ray ray, IntersectionRecord intersectionRecord, Scene scene) {
Intersectable waterModel = null;

// TODO this shouldn't be checked at intersection time, but rather when loading chunks.
boolean isWaterloggedFull = false;
if (waterlogged) {
// If this block is waterlogged, we check if the block above it is either water or
// waterlogged (isWaterFilled()).
// If it is, then the water model to use should be the `Block.FULL_BLOCK` full block model.
// Otherwise, the `WaterModel.NOT_FULL_BLOCK` model for a full water block exposed to air
// will be used.

int x = (int) QuickMath.floor(ray.o.x + ray.d.x * Constants.OFFSET);
int y = (int) QuickMath.floor(ray.o.y + ray.d.y * Constants.OFFSET);
int z = (int) QuickMath.floor(ray.o.z + ray.d.z * Constants.OFFSET);
isWaterloggedFull = scene.getWorldOctree().getMaterial(x, y + 1, z, scene.getPalette()).isWaterFilled();
if (ray.getCurrentMedium().isWater()) {
if (!isWaterloggedFull) {
waterModel = WaterModel.WATER_TOP;
}
} else {
waterModel = (isWaterloggedFull) ? Block.FULL_BLOCK : WaterModel.NOT_FULL_BLOCK;
}
}

IntersectionRecord modelIntersect = new IntersectionRecord();
IntersectionRecord waterIntersect = new IntersectionRecord();

boolean modelHit = model.intersect(ray, modelIntersect, scene);
boolean waterHit = false;
if (waterModel != null) {
waterHit = waterModel.closestIntersection(ray, waterIntersect, scene);
}

// Whether the ray hits the top of the water model when the water model is not a full block.
boolean hitTop = waterHit && !isWaterloggedFull && waterIntersect.n.y > 0 && ray.d.dot(waterIntersect.n) > 0;

if (ray.getCurrentMedium() == this) {
// Ray is currently traversing this block.
if (modelHit) {
intersectionRecord.setNormal(modelIntersect);
if (ray.d.dot(intersectionRecord.n) > 0) {
// Ray is exiting the block.
Vector3 o = new Vector3(ray.o);
if (onEdge(o, ray.d, modelIntersect.distance)) {
return false;
}
intersectionRecord.n.scale(-1);
intersectionRecord.shadeN.scale(-1);

Block waterPlaneMaterial = scene.waterPlaneMaterial(ray.o.rScaleAdd(intersectionRecord.distance, ray.d));
if (waterlogged) {
if (isWaterloggedFull || o.y < 1 - WaterModel.TOP_BLOCK_GAP) {
intersectionRecord.material = scene.getPalette().water;
Water.INSTANCE.getColor(intersectionRecord);
} else {
intersectionRecord.material = waterPlaneMaterial;
waterPlaneMaterial.getColor(intersectionRecord);
}
} else {
intersectionRecord.material = waterPlaneMaterial;
waterPlaneMaterial.getColor(intersectionRecord);
}
} else {
// Ray is not exiting the block, but it hit a surface inside the block.
intersectionRecord.color.set(modelIntersect.color);
}
intersectionRecord.distance = modelIntersect.distance;
intersectionRecord.flags = modelIntersect.flags;
return true;
} else {
return false;
}
} else if (ray.getCurrentMedium().isWater()) {
// Ray is currently traversing water, whether that be the water part of the block (if it be
// waterlogged), or water outside the block.
if (!waterlogged && (!modelHit || modelIntersect.distance > Constants.EPSILON)) {
// Ray is currently traversing water, and the block is not waterlogged.
// Therefore, if we don't hit the block model, or the distance to the model hit is greater
// than zero, we need to hit the "air part" of the block, to take the material properties
// into account. If the intersection point is below the water plane, the water plane
// material is hit instead of air.
intersectionRecord.distance = 0;
Block waterPlaneMaterial = scene.waterPlaneMaterial(ray.o);
intersectionRecord.material = waterPlaneMaterial;
waterPlaneMaterial.getColor(intersectionRecord);
return true;
}
if (modelHit && modelIntersect.distance < waterIntersect.distance - Constants.EPSILON) {
// The ray hit the block model, and the distance to the block model intersection is closer
// than the distance to the water model intersection. As such, we hit the block model.
intersectionRecord.distance = modelIntersect.distance;
intersectionRecord.setNormal(modelIntersect);
intersectionRecord.color.set(modelIntersect.color);
intersectionRecord.flags = modelIntersect.flags;
return true;
} else if (hitTop) {
// The ray is traversing water, did not hit the block model, and has hit the top of the
// water model (not full block).
intersectionRecord.distance = waterIntersect.distance;
intersectionRecord.setNormal(waterIntersect);

Ray testRay = new Ray(ray);
testRay.o.scaleAdd(intersectionRecord.distance, testRay.d);
intersectionRecord.material = scene.waterPlaneMaterial(testRay.o);
intersectionRecord.material.getColor(intersectionRecord);

Vector3 shadeNormal = scene.getCurrentWaterShader().doWaterShading(testRay, intersectionRecord, scene.getAnimationTime());
intersectionRecord.shadeN.set(shadeNormal);

intersectionRecord.n.scale(-1);
intersectionRecord.shadeN.scale(-1);
return true;
} else {
return false;
}
} else {
// The ray is currently traversing any other material, such as air, glass, WaterPlane, etc.
if (!waterlogged && (!modelHit || modelIntersect.distance > Constants.EPSILON)) {
// As before, if the block is not waterlogged, we need to make sure to take the "air part"
// into account, whether that be air, or the water plane.
Block waterPlaneMaterial = scene.waterPlaneMaterial(ray.o);
if (ray.getCurrentMedium() != waterPlaneMaterial) {
intersectionRecord.distance = 0;
intersectionRecord.material = waterPlaneMaterial;
waterPlaneMaterial.getColor(intersectionRecord);
return true;
}
}
if (modelHit && modelIntersect.distance < waterIntersect.distance + Constants.EPSILON) {
// The ray hit the block model, and the distance is closer than the distance to the water
// model intersection.
intersectionRecord.distance = modelIntersect.distance;
intersectionRecord.setNormal(modelIntersect);
intersectionRecord.color.set(modelIntersect.color);
intersectionRecord.flags = modelIntersect.flags;
return true;
} else if (waterHit) {
// The water model intersection is closer.
intersectionRecord.distance = waterIntersect.distance;
intersectionRecord.setNormal(waterIntersect);
Water.INSTANCE.getColor(intersectionRecord);
intersectionRecord.material = scene.getPalette().water;
intersectionRecord.flags = 0;

if (intersectionRecord.n.y > 0) {
// Add water shading.
Ray testRay = new Ray(ray);
testRay.o.scaleAdd(intersectionRecord.distance, testRay.d);
Vector3 shadeNormal = scene.getCurrentWaterShader().doWaterShading(testRay, intersectionRecord, scene.getAnimationTime());
intersectionRecord.shadeN.set(shadeNormal);
}
return true;
} else {
// The ray did not hit anything.
return false;
}
}
}

@Override
public boolean intersect(Ray ray, Scene scene) {
return model.intersect(ray, scene);
public boolean isInside(Ray ray) {
return model.isInside(ray);
}

@Override
Expand Down
33 changes: 15 additions & 18 deletions chunky/src/java/se/llbit/chunky/block/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,19 @@
import se.llbit.chunky.world.biome.Biome;
import se.llbit.json.JsonString;
import se.llbit.json.JsonValue;
import se.llbit.math.AABB;
import se.llbit.math.Ray;
import se.llbit.math.Vector3;
import se.llbit.math.*;
import se.llbit.nbt.CompoundTag;
import se.llbit.nbt.Tag;

import java.util.Random;

public abstract class Block extends Material {
private final static AABB block = new AABB(0, 1, 0, 1, 0, 1);
public static final AABB FULL_BLOCK = new AABB(0, 1, 0, 1,0, 1);

/**
* Set to true if there is a local intersection model for this block. If this is set to
* <code>false</code> (default), this block is assumed to be an opaque cube block and {@link
* #intersect(Ray, Scene)} will never be called.
* #intersect(Ray, IntersectionRecord, Scene)} will never be called.
*/
public boolean localIntersect = false;

Expand Down Expand Up @@ -50,14 +48,14 @@ public int faceCount() {
* @param rand Random number source.
*/
public void sample(int face, Vector3 loc, Random rand) {
block.sampleFace(face, loc, rand);
FULL_BLOCK.sampleFace(face, loc, rand);
}

/**
* Get the surface area of this face of the block.
*/
public double surfaceArea(int face) {
return block.faceSurfaceArea(face);
return FULL_BLOCK.faceSurfaceArea(face);
}

/**
Expand All @@ -69,17 +67,7 @@ public double surfaceArea(int face) {
* @param scene Scene
* @return True if the ray hit this block, false if not
*/
public boolean intersect(Ray ray, Scene scene) {
ray.t = Double.POSITIVE_INFINITY;
if (block.intersect(ray)) {
float[] color = texture.getColor(ray.u, ray.v);
if (color[3] > Ray.EPSILON) {
ray.color.set(color);
ray.distance += ray.tNext;
ray.o.scaleAdd(ray.tNext, ray.d);
return true;
}
}
public boolean intersect(Ray ray, IntersectionRecord intersectionRecord, Scene scene) {
return false;
}

Expand Down Expand Up @@ -148,6 +136,15 @@ public Tag getNewTagWithBlockEntity(Tag blockTag, CompoundTag entityTag) {
return null;
}

/**
* Checks whether the given ray is inside this block.
*/
public boolean isInside(Ray ray) {
double ix = ray.o.x - QuickMath.floor(ray.o.x);
double iy = ray.o.y - QuickMath.floor(ray.o.y);
double iz = ray.o.z - QuickMath.floor(ray.o.z);
return FULL_BLOCK.inside(new Vector3(ix, iy, iz));
}
/**
* Does this block use biome tint for its rendering
*/
Expand Down
Loading