Skip to content

Commit

Permalink
Refactor: Improved ray tracing code a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
Martomate committed Apr 7, 2024
1 parent 4eb5d40 commit 867676e
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 62 deletions.
156 changes: 103 additions & 53 deletions game/src/main/scala/hexacraft/game/ray.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package hexacraft.game

import hexacraft.game.PointHexagon.{Region, Slice}
import hexacraft.math.MathUtils.oppositeSide
import hexacraft.world.{Camera, CylinderSize, HexBox}
import hexacraft.world.block.{Block, BlockState}
Expand Down Expand Up @@ -31,21 +32,26 @@ class RayTracer(camera: Camera, maxDistance: Double)(using CylinderSize) {

val points = PointHexagon.fromHexBox(BlockState.boundingBox, current, camera)

val index = sideIndex(ray, points)
val side = actualSide(ray, points, index)
val normal = sideNormal(points, index, side)
val (slice, region) = points.intersectionSide(ray)
val normal = points.normal(slice, region)

if ray.v.dot(normal) > 0 then { // TODO: this is a temporary fix for ray-loops
System.err.println("At least one bug has not been figured out yet! (Rayloops in RayTracer.trace.traceIt)")
return None
}

val pointOnSide = if side == 0 then points.up(index) else points.down(index)
val pointOnSide = if region == Region.Ceiling then points.up(slice) else points.down(slice)
val distance = Math.abs(pointOnSide.dot(normal) / ray.v.dot(normal)) // abs may be needed (a/-0)
if distance > maxDistance * CylinderSize.y60 then {
return None
}

val side = region match {
case Region.Ceiling => 0
case Region.Floor => 1
case Region.Wall => slice + 2
}

val hitBlockCoords = current.offset(NeighborOffsets(side))
if blockTouched(blockAtCoords, ray, hitBlockCoords) then {
return Some((hitBlockCoords, Some(oppositeSide(side))))
Expand All @@ -54,42 +60,6 @@ class RayTracer(camera: Camera, maxDistance: Double)(using CylinderSize) {
traceIt(hitBlockCoords, ray, blockAtCoords, ttl - 1)
}

private def sideNormal(points: PointHexagon, index: Int, side: Int) = {
val PA = new Vector3d
val PB = new Vector3d

if side == 0 then {
points.up((index + 1) % 6).sub(points.up(index), PA)
points.up((index + 5) % 6).sub(points.up(index), PB)
} else if side == 1 then {
points.down((index + 5) % 6).sub(points.down(index), PA)
points.down((index + 1) % 6).sub(points.down(index), PB)
} else {
points.down((index + 1) % 6).sub(points.down(index), PA)
points.up(index).sub(points.down(index), PB)
}

PA.cross(PB, new Vector3d())
}

private def sideIndex(ray: Ray, points: PointHexagon) = {
if ray.toTheRight(points.down(0), points.up(0)) then {
(5 to 1 by -1).find(index => !ray.toTheRight(points.down(index), points.up(index))).getOrElse(0)
} else {
(1 to 5).find(index => ray.toTheRight(points.down(index), points.up(index))).getOrElse(6) - 1
}
}

private def actualSide(ray: Ray, points: PointHexagon, index: Int) = {
if ray.toTheRight(points.up(index), points.up((index + 1) % 6)) then {
0
} else if !ray.toTheRight(points.down(index), points.down((index + 1) % 6)) then {
1
} else {
index + 2
}
}

private def blockTouched(
blockAtCoords: BlockRelWorld => Option[BlockState],
ray: Ray,
Expand All @@ -100,14 +70,32 @@ class RayTracer(camera: Camera, maxDistance: Double)(using CylinderSize) {
(0 until 8).exists(side => {
val boundingBox = block.blockType.bounds(block.metadata)
val points = PointHexagon.fromHexBox(boundingBox, hitBlockCoords, camera)
ray.intersectsPolygon(points, side)
ray.intersectsPolygon(points, BlockFace.fromInt(side))
})
case _ =>
false
}
}
}

enum BlockFace {
case Top
case Bottom

/** @param index 0 to 5, ccw */
case Side(index: Int)
}

object BlockFace {
def fromInt(i: Int): BlockFace = {
i match {
case 0 => BlockFace.Top
case 1 => BlockFace.Bottom
case _ => BlockFace.Side(i - 2)
}
}
}

class Ray(val v: Vector3d) {

/** @return
Expand All @@ -118,19 +106,32 @@ class Ray(val v: Vector3d) {
down.dot(up.cross(v, new Vector3d)) <= 0
}

def intersectsPolygon(points: PointHexagon, side: Int): Boolean = {
def intersectsPolygon(points: PointHexagon, side: BlockFace): Boolean = {
val rightSeq = side match {
case 0 => for index <- 0 until 6 yield this.toTheRight(points.up(index), points.up((index + 1) % 6))
case 1 => for index <- 0 until 6 yield this.toTheRight(points.down(index), points.down((index + 1) % 6))
case _ =>
case BlockFace.Top =>
for index <- 0 until 6 yield {
val a = points.up(index)
val b = points.up((index + 1) % 6)
this.toTheRight(a, b)
}
case BlockFace.Bottom =>
for index <- 0 until 6 yield {
val a = points.down(index)
val b = points.down((index + 1) % 6)
this.toTheRight(a, b)
}
case BlockFace.Side(s) =>
val order = Seq(0, 1, 3, 2)

for index <- 0 until 4 yield {
val aIdx = (order(index) % 2 + side - 2) % 6
val PA = if order(index) / 2 == 0 then points.up(aIdx) else points.down(aIdx)
val aIdx = (order(index) % 2 + s) % 6
val bIdx = (order((index + 1) % 4) % 2 + s) % 6

val bIdx = (order((index + 1) % 4) % 2 + side - 2) % 6
val PB = if order((index + 1) % 4) / 2 == 0 then points.up(bIdx) else points.down(bIdx)
val aUp = order(index) / 2 == 0
val bUp = order((index + 1) % 4) / 2 == 0

val PA = if aUp then points.up(aIdx) else points.down(aIdx)
val PB = if bUp then points.up(bIdx) else points.down(bIdx)

this.toTheRight(PA, PB)
}
Expand Down Expand Up @@ -172,11 +173,60 @@ object PointHexagon {
.toNormalCoords(CylCoords(camera.view.position))
.toVector3d
)
new PointHexagon(points)
new PointHexagon(points.take(6).toArray, points.drop(6).toArray)
}

type Slice = Int

enum Region {
case Ceiling
case Floor
case Wall
}
}

class PointHexagon(points: Seq[Vector3d]) {
def up(idx: Int): Vector3d = points(idx)
def down(idx: Int): Vector3d = points(idx + 6)
class PointHexagon(val up: Array[Vector3d], val down: Array[Vector3d]) {
extension (index: Int) {
private inline def inc: Int = (index + 1) % 6
private inline def dec: Int = (index + 5) % 6
}

def intersectionSide(ray: Ray): (Slice, Region) = {
// check the sides of the sides of a hexagonal pillar
val index = if ray.toTheRight(this.down(0), this.up(0)) then {
(5 to 1 by -1).find(index => !ray.toTheRight(this.down(index), this.up(index))).getOrElse(0)
} else {
(1 to 5).find(index => ray.toTheRight(this.down(index), this.up(index))).getOrElse(6) - 1
}

// check for intersection with the ceiling or the floor
val region = if ray.toTheRight(this.up(index), this.up(index.inc)) then {
Region.Ceiling
} else if !ray.toTheRight(this.down(index), this.down(index.inc)) then {
Region.Floor
} else {
Region.Wall
}

(index, region)
}

def normal(index: Slice, region: Region): Vector3d = {
val PA = new Vector3d
val PB = new Vector3d

region match {
case Region.Ceiling =>
this.up(index.inc).sub(this.up(index), PA)
this.up(index.dec).sub(this.up(index), PB)
case Region.Floor =>
this.down(index.dec).sub(this.down(index), PA)
this.down(index.inc).sub(this.down(index), PB)
case Region.Wall =>
this.down(index.inc).sub(this.down(index), PA)
this.up(index).sub(this.down(index), PB)
}

PA.cross(PB, new Vector3d())
}
}
9 changes: 0 additions & 9 deletions game/src/main/scala/hexacraft/world/collision.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,6 @@ class CollisionDetector(world: BlocksInWorld)(using cylSize: CylinderSize) {
return (box.pos.toVector3d, new Vector3d)
}

resultAfterCollisionImpl(box, minDist, reflectionDir, ttl)
}

private def resultAfterCollisionImpl(
box: MovingBox,
minDist: Double,
reflectionDir: Int,
ttl: Int
): (Vector3d, Vector3d) = {
val normal = reflDirsCyl(reflectionDir).toVector3d.normalize()
val newPos = box.pos.offset(box.velocity.x * minDist, box.velocity.y * minDist, box.velocity.z * minDist)
val vel = box.velocity.toVector3d.mul(1 - minDist)
Expand Down

0 comments on commit 867676e

Please sign in to comment.