diff --git a/core/src/main/java/com/seibel/lod/core/util/RayCastUtil.java b/core/src/main/java/com/seibel/lod/core/util/RayCastUtil.java new file mode 100644 index 000000000..8905a9d8b --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/util/RayCastUtil.java @@ -0,0 +1,180 @@ +/* + * This file is part of the Distant Horizons mod (formerly the LOD Mod), + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2022 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import com.seibel.lod.core.util.math.Vec3d; +import com.seibel.lod.core.util.math.Vec3f; +import com.seibel.lod.core.util.math.Vec3i; + +/** + * @author James Seibel + * @version 2022-11-19 + */ +public class RayCastUtil +{ + + public static boolean rayIntersectsCube(Vec3d rayStartingPos, Vec3f rayDirection, Vec3i cubeMinPos, int cubeWidth) + { + // the ray must intersect all 3 axis in order to have gone through the cube + return rayIntersectsSquare(rayStartingPos.x, rayStartingPos.z, rayDirection.x, rayDirection.z, cubeMinPos.x, cubeMinPos.z, cubeWidth) && + rayIntersectsSquare(rayStartingPos.x, rayStartingPos.y, rayDirection.x, rayDirection.y, cubeMinPos.x, cubeMinPos.y, cubeWidth); + } + + /** + * this function works for any perpendicular axis, X and Y are just for simplicity and could easily be replaced with X, Y, or Z + * + * @param rayX the ray's starting X position + * @param rayY the ray's starting Z position + * @param squareMinX the square's X corner closest to negative infinity + * @param squareMinY the square's Y corner closest to negative infinity + */ + public static boolean rayIntersectsSquare( + double rayX, double rayY, double rayXDirection, double rayYDirection, + double squareMinX, double squareMinY, double squareWidth) + { + double roundingValue = 0.05; + + // determine the other corner of the square + double squareMaxX = squareMinX + squareWidth; + double squareMaxY = squareMinY + squareWidth; + + + // check if the ray originates in the square + if (rayX >= squareMinX && rayX <= squareMaxX && + rayY >= squareMinY && rayY <= squareMaxY) + { + return true; + } + + + + if (isRoughly(rayXDirection, 0, roundingValue) && isRoughly(rayYDirection, 0, roundingValue)) + { + // slope is in a direction perpendicular to this ray + + // this ray can be treated like a point, + // checking if the point originated inside the square + // should catch if this was true + return false; + } + else if (isRoughly(Math.abs(rayYDirection), 1, roundingValue)) + { + // slope is straight up or down + + // is the ray pointing towards the square? + if ((rayYDirection > 0 && rayY > squareMaxY) || // up + (rayYDirection < 0 && rayY < squareMinY)) // down + { + // the ray is pointing away from the square + return false; + } + else + { + // check if the ray's X value is between the square's left and right sides + return rayX >= squareMinX && rayX <= squareMaxX; + } + } + else if (isRoughly(rayYDirection, 0, roundingValue)) + { + // slope is 0 (horizontal line) + + // is the ray pointing towards the square? + if ((rayXDirection > 0 && rayX > squareMaxX) || // right + (rayXDirection < 0 && rayX < squareMinX)) // left + { + // the ray is pointing away from the square + return false; + } + else + { + // check if the ray's Y value is between the square's top and bottom sides + return rayY >= squareMinY && rayY <= squareMaxY; + } + } + else + { + // slope is a valid range (between -infinity and infinity) + double slope = rayYDirection / rayXDirection; + + // move the square into ray space (where the ray is at the origin) + squareMinX -= rayX; + squareMaxX -= rayX; + + squareMinY -= rayY; + squareMaxY -= rayY; + + + boolean intersectsX = false; + boolean intersectsY = false; + + + + // ray Y intersect + // y = mx + double yIntersectMin = slope * squareMinX; + double yIntersectMax = slope * squareMaxX; + + // does the intersection happen before the ray's origin? + if (yIntersectMin <= rayY && (yIntersectMax <= rayY)) + { + return false; + } + // does the line intersect the square? + else if (yIntersectMin >= squareMinY && yIntersectMin <= squareMaxY) + { + intersectsY = true; + } + else if (yIntersectMax >= squareMinY && yIntersectMax <= squareMaxY) + { + intersectsY = true; + } + + + // ray X intersect + // x = y/m + double xIntersectMin = squareMinY / slope; + double xIntersectMax = squareMaxY / slope; + + // does the intersection happen before the ray's origin? + if (xIntersectMin <= rayX && (xIntersectMax <= rayX)) + { + return false; + } + // does the line intersect the square? + else if (xIntersectMin >= squareMinX && xIntersectMin <= squareMaxX) + { + intersectsX = true; + } + else if (xIntersectMax >= squareMinX && xIntersectMax <= squareMaxX) + { + intersectsX = true; + } + + + // if the ray intersects both the top and side of the square, that means + // the ray intersects the square as a whole + return intersectsX && intersectsY; + } + } + /** used to get around floating point number rounding errors */ + private static boolean isRoughly(double input, double equalsVal, double errorValue) { return input >= equalsVal - errorValue && input <= equalsVal + errorValue; } + + +} diff --git a/core/src/test/java/tests/RaycastingTest.java b/core/src/test/java/tests/RaycastingTest.java new file mode 100644 index 000000000..3e02c03b7 --- /dev/null +++ b/core/src/test/java/tests/RaycastingTest.java @@ -0,0 +1,182 @@ +/* + * This file is part of the Distant Horizons mod (formerly the LOD Mod), + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2022 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package tests; + +import com.seibel.lod.core.api.external.methods.data.DhApiTerrainDataRepo; +import com.seibel.lod.core.util.RayCastUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author James Seibel + * @version 2022-11-19 + */ +public class RaycastingTest +{ + + @Test + public void DemoTest() + { + Assert.assertTrue("Example test 1", true); + + double rayX, rayY; + double xDir, yDir; + + // 1x1 square at (1,1) - (2,2) + double squareMinX = 1; + double squareMinY = 1; + int squareWidth = 1; + + + + //============// + // horizontal // + //============// + + // ray points right - direction <1,0> + xDir = 1; + yDir = 0; + + // ray origin left of square + rayX = 0; + testRay(false, rayX, 0, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 0.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(true, rayX, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, rayX, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, rayX, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(false, rayX, 2.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 3, xDir, yDir, squareMinX, squareMinY, squareWidth); + + // ray origin right of square + rayX = 2.5; + testRay(false, rayX, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + + //==========// + // vertical // + //==========// + + xDir = 0; + yDir = 1; + + // ray points up - direction <0,1> + rayY = 0; + + // ray origin below square + testRay(false, 0, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 0.5, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(true, 1, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 1.5, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 2, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(false, 2.5, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 3, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + // ray origin in square + testRay(true, 1, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 1.5, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 2, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + // ray origin above square + rayY = 2.5; + testRay(false, 1, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 1.5, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 2, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + + //=======// + // point // + //=======// + + // AKA the slope is perpendicular to this plane + // direction <0,0> + xDir = 0; + yDir = 0; + testRay(false, 0, 0, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 0.5, 0.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(true, 1, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 1.5, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 2, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(false, 2.5, 2.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, 3, 3, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + + //==========// + // diagonal // + //==========// + + // ray points up right - direction <4,3> (a slope of 3/4) + xDir = 4; + yDir = 3; + + // ray origin bottom left of square + rayX = 0; + testRay(false, rayX, -1, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(true, rayX, -0.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, rayX, 0, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, rayX, 0.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, rayX, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + + testRay(false, rayX, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + // ray origin in square + testRay(true, 1, 1, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 1.5, 1.5, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(true, 2, 2, xDir, yDir, squareMinX, squareMinY, squareWidth); + + + // ray origin right of square + rayX = 2.5; + rayY = (yDir/xDir) * rayX; // y = mx + b // b is the constants defined below + testRay(false, rayX, -0.5 + rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 0 + rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 0.5 + rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + testRay(false, rayX, 1 + rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + + } + + private static void testRay(boolean expectedToIntersect, double rayX, double rayY, double xDir, double yDir, double squareMinX, double squareMinY, double squareWidth) + { + boolean intersects = RayCastUtil.rayIntersectsSquare(rayX, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth); + Assert.assertEquals(failMessage(rayX, rayY, xDir, yDir, squareMinX, squareMinY, squareWidth), expectedToIntersect, intersects); + } + + private static String failMessage(double rayX, double rayY, double xDir, double yDir, double squareMinX, double squareMinY, double squareWidth) + { + return "ray: [" + rayX + ", " + rayY + "] <" + xDir + ", " + yDir + "> square: [" + squareMinX + ", " + squareMinY + "] - [" + (squareMinX+squareWidth) + ", " + (squareMinY+squareWidth) + "]"; + } + + +}