Compare commits

..

1 Commits

Author SHA1 Message Date
e0b04f0efd feat: added complex shape raycasting 2024-07-22 18:54:46 +02:00
11 changed files with 298 additions and 0 deletions

View File

@ -44,6 +44,7 @@ sourceSets {
}
loom {
accessWidenerPath = file("src/main/resources/reframed.accesswidener")
runs {
// This adds a new gradle task that runs the datagen API: "gradlew runDatagen"
datagen {

View File

@ -103,6 +103,7 @@ public class ReFramed implements ModInitializer {
BLUEPRINT = registerItem("blueprint" , new ReFramedBlueprintItem(new Item.Settings()));
BLUEPRINT_WRITTEN = registerItem("blueprint_written" , new ReFramedBlueprintWrittenItem(new Item.Settings().maxCount(1)));
registerBlock("test", new TestBlock(cp(Blocks.OAK_PLANKS).nonOpaque())); // TODO remove
REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"),
BlockEntityType.Builder.create(

View File

@ -0,0 +1,36 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.client.voxel.MorphVoxel;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
public class TestBlock extends Block {
public static MorphVoxel voxel = new MorphVoxel(createCuboidShape(1, 1, 1, 15, 15, 15),
new Vec3d[] {
new Vec3d(0, 0, 0),
new Vec3d(1, 0, 0),
new Vec3d(1, 1, 1),
new Vec3d(0, 1, 1),
new Vec3d(0, 1, 1),
new Vec3d(1, 1, 1),
new Vec3d(1, 0, 1),
new Vec3d(0, 0, 1)
});
public TestBlock(Settings settings) {
super(settings);
}
@Override
@SuppressWarnings("deprecation")
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return voxel;
}
}

View File

@ -0,0 +1,7 @@
package fr.adrien1106.reframed.client.util;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
public record RaycastResult(Vec3d pos, Direction face, boolean inside) {
}

View File

@ -0,0 +1,42 @@
package fr.adrien1106.reframed.client.voxel;
import fr.adrien1106.reframed.client.util.RaycastResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import java.util.List;
import java.util.Objects;
public class MorphBox extends Box {
protected List<Triangle> triangles;
public MorphBox(List<Triangle> triangles) {
super(new BlockPos(0, 0, 0));
this.triangles = triangles;
}
public RaycastResult triangleRaycast(Vec3d min, Vec3d max) {
return triangles.stream()
.map(triangle -> {
Vec3d inter = triangle.intersection(min, max);
if (inter == null) return null;
return new RaycastResult(
inter,
triangle.face(),
false
);
})
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> isInside(min) ? new RaycastResult(
min,
triangles.get(0).face(),
true
) : null);
}
public boolean isInside(Vec3d point) {
return triangles.stream().allMatch(triangle -> triangle.after(point));
}
}

View File

@ -0,0 +1,100 @@
package fr.adrien1106.reframed.client.voxel;
import fr.adrien1106.reframed.client.util.RaycastResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.*;
import net.minecraft.util.shape.SimpleVoxelShape;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Represents a VoxelShape that can be morphed using its 8 vertices.
* the vertices are distributed as follows:
* <pre><code>
* E--------F ABCD -> NORTH
* ^ /| /| EFGH -> SOUTH
* |/ | / | AHED -> EAST
* D--------C | BCGF -> WEST
* | |/ | | ABGH -> DOWN
* | H-----|--G CDFE -> UP
* | / | /
* |/ |/
* A--------B->
* </code></pre>
*/
public class MorphVoxel extends SimpleVoxelShape {
private static final int[][] EDGE_INDICES = {
{0, 1},
{1, 2},
{2, 3},
{3, 0},
{0, 7},
{1, 6},
{2, 5},
{3, 4},
{4, 5},
{5, 6},
{6, 7},
{7, 4}
};
private static final int[][] FACE_INDICES = {
{7, 6, 1, 0},
{5, 4, 3, 2},
{0, 1, 2, 3},
{4, 5, 6, 7},
{0, 3, 4, 7},
{6, 5, 2, 1}
};
protected final Vec3d[] v;
MorphBox box;
public MorphVoxel(VoxelShape wrapper, Vec3d[] vertices) {
super(wrapper.voxels);
List<Triangle> triangles = new ArrayList<>();
Stream.of(Direction.values()).forEach(dir -> {
int i = dir.ordinal();
List<Vec3d> v = IntStream.range(0, 4).mapToObj(j -> { // remove matching vertices
if (vertices[FACE_INDICES[i][j]].equals(vertices[FACE_INDICES[i][(j+1)%4]])) return null;
return vertices[FACE_INDICES[i][j]];
}).filter(Objects::nonNull).toList();
if (v.size() < 3) return; // skip if there are less than 3 vertices (e.g. a line/dot)
triangles.add(Triangle.of(dir,v));
});
box = new MorphBox(triangles);
v = vertices;
}
@Nullable
@Override
public BlockHitResult raycast(Vec3d start, Vec3d end, BlockPos pos) {
RaycastResult result = box.triangleRaycast(start.subtract(pos.getX(), pos.getY(), pos.getZ()), end.subtract(pos.getX(), pos.getY(), pos.getZ()));
return result == null
? null
: new BlockHitResult(
result.pos().add(pos.getX(), pos.getY(), pos.getZ()),
result.face(),
pos,
result.inside()
);
}
@Override
public void forEachEdge(VoxelShapes.BoxConsumer consumer) {
IntStream.range(0, 12).forEach(i -> {
Vec3d a = v[EDGE_INDICES[i][0]];
Vec3d b = v[EDGE_INDICES[i][1]];
consumer.consume(a.x, a.y, a.z, b.x, b.y, b.z);
});
}
}

View File

@ -0,0 +1,78 @@
package fr.adrien1106.reframed.client.voxel;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import java.util.List;
import java.util.stream.IntStream;
/**
* Represents a Face of a MorphVoxel.
* @param v - list of vertices on the same plane
* @param e - list of the outer edges
* @param n - normal of the face
* @param face - the direction of the face
*/
public record Triangle(Vec3d[] v, Vec3d[] e, Vec3d n, Direction face) {
/**
* Creates a Triangle from a list of vertices.
* @param face - the direction of the face
* @param vertices - list of vertices on the same plane
* @return the Triangle
*/
public static Triangle of(Direction face, List<Vec3d> vertices) {
assert vertices.size() >= 3;
Vec3d[] v = vertices.toArray(new Vec3d[0]);
// compute the edges
Vec3d[] e = new Vec3d[v.length];
IntStream.range(0, v.length).forEach(i -> e[i] = v[(i + 1) % v.length].subtract(v[i]));
// compute the normal
Vec3d n = v[1].subtract(v[0]).crossProduct(v[2].subtract(v[0])).normalize();
return new Triangle(v, e, n, face);
}
/**
* Computes the intersection of a ray with the triangle.
* @param start - the start of the ray
* @param end - the end of the ray
* @return the intersection point or null if there is no intersection
*/
public Vec3d intersection(Vec3d start, Vec3d end) {
Vec3d start_v0 = start.subtract(v[0]);
Vec3d end_v1 = end.subtract(v[1]);
// check if the ray intersects the plane
if (n.dotProduct(start_v0) * n.dotProduct(end_v1) >= 0) return null;
Vec3d ray = end.subtract(start);
double direction = n.dotProduct(ray);
// plane normal is facing away from the ray
if (direction < 0) return null;
// get Intersection point
double t = -n.dotProduct(start_v0) / direction;
Vec3d intersection = start.add(ray.multiply(t));
// check if the intersection is inside the triangle
for (int i = 0; i < v.length; i++) {
Vec3d edge = e[i];
Vec3d edge_intersection = intersection.subtract(v[i]);
if (edge_intersection.length() < 1e-6) break; // intersection is on the vertex
double a = n.dotProduct(edge.crossProduct(edge_intersection));
if (a <= 0) return null; // intersection is outside the triangle
if (a <= edge.length() * 1e-6) break; // intersection is on the edge
}
return intersection;
}
public boolean after(Vec3d point) {
return n.dotProduct(point.subtract(v[0])) > 0;
}
}

View File

@ -53,6 +53,8 @@ public class GBlockstate extends FabricModelProvider {
providers.put(ReFramedPostBlock.class, new Post());
providers.put(ReFramedFenceBlock.class, new Fence());
providers.put(ReFramedPostFenceBlock.class, new PostFence());
providers.put(TestBlock.class, new Test());
}
public GBlockstate(FabricDataOutput output) {

View File

@ -0,0 +1,26 @@
package fr.adrien1106.reframed.generator.block;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import net.minecraft.block.Block;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.VariantsBlockStateSupplier;
import net.minecraft.util.Identifier;
import static net.minecraft.data.client.VariantSettings.Rotation.R0;
public class Test implements BlockStateProvider {
@Override
public BlockStateSupplier getMultipart(Block block) {
return VariantsBlockStateSupplier.create(
block,
GBlockstate.variant(
new Identifier("block/barrier"),
true,
R0, R0
)
);
}
}

View File

@ -25,6 +25,7 @@
"mixins": [
"reframed.mixins.json"
],
"accessWidener": "reframed.accesswidener",
"depends": {
"minecraft": "${minecraft_version}",
"fabricloader": "^${loader_version}",

View File

@ -0,0 +1,4 @@
accessWidener v2 named
accessible field net/minecraft/util/shape/VoxelShape voxels Lnet/minecraft/util/shape/VoxelSet;
extendable class net/minecraft/util/shape/SimpleVoxelShape