• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Martomate / Hexacraft / 7351367576

28 Dec 2023 06:50PM UTC coverage: 51.185% (-0.1%) from 51.312%
7351367576

push

github

Martomate
Refactor: Made Chunk not know if it has been saved to file

9 of 12 new or added lines in 2 files covered. (75.0%)

110 existing lines in 32 files now uncovered.

2829 of 5527 relevant lines covered (51.19%)

0.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.54
/game/src/main/scala/hexacraft/game/ray.scala
1
package hexacraft.game
2

3
import hexacraft.math.MathUtils.oppositeSide
4
import hexacraft.world.{Camera, CylinderSize, HexBox}
5
import hexacraft.world.block.{Block, BlockState}
6
import hexacraft.world.coord.*
7

8
import org.joml.{Vector2fc, Vector3d, Vector4f}
9

10
import scala.annotation.tailrec
11

12
class RayTracer(camera: Camera, maxDistance: Double)(using CylinderSize):
1✔
13
  def trace(
14
      ray: Ray,
15
      blockAtCoords: BlockRelWorld => Option[BlockState]
16
  ): Option[(BlockRelWorld, Option[Int])] =
1✔
17
    if blockTouched(blockAtCoords, ray, camera.blockCoords)
1✔
18
    then Some((camera.blockCoords, None))
1✔
19
    else traceIt(camera.blockCoords, ray, blockAtCoords, 1000)
20

21
  @tailrec
1✔
22
  private def traceIt(
23
      current: BlockRelWorld,
24
      ray: Ray,
25
      blockAtCoords: BlockRelWorld => Option[BlockState],
26
      ttl: Int
27
  ): Option[(BlockRelWorld, Option[Int])] =
28
    if (ttl < 0) // TODO: this is a temporary fix for ray-loops
×
29
      return None
30

1✔
31
    val points = PointHexagon.fromHexBox(BlockState.boundingBox, current, camera)
32

1✔
33
    val index = sideIndex(ray, points)
1✔
34
    val side = actualSide(ray, points, index)
1✔
35
    val normal = sideNormal(points, index, side)
36

1✔
37
    if ray.v.dot(normal) <= 0 then // TODO: this is a temporary fix for ray-loops
1✔
38
      val pointOnSide =
1✔
39
        if side == 0 then points.up(index) else points.down(index)
40
      val distance =
1✔
41
        Math.abs(pointOnSide.dot(normal) / ray.v.dot(normal)) // abs may be needed (a/-0)
42
      if distance <= maxDistance * CylinderSize.y60 then
1✔
43
        val hitBlockCoords = current.offset(NeighborOffsets(side))
44

1✔
45
        if blockTouched(blockAtCoords, ray, hitBlockCoords)
1✔
46
        then Some((hitBlockCoords, Some(oppositeSide(side))))
1✔
47
        else traceIt(hitBlockCoords, ray, blockAtCoords, ttl - 1)
1✔
48
      else None
49
    else
×
50
      System.err.println(
51
        "At least one bug has not been figured out yet! (Rayloops in RayTracer.trace.traceIt)"
52
      )
53
      None
54

1✔
55
  private def sideNormal(points: PointHexagon, index: Int, side: Int) =
1✔
56
    val PA = new Vector3d
1✔
57
    val PB = new Vector3d
58

59
    if side == 0 then
×
60
      points.up((index + 1) % 6).sub(points.up(index), PA)
×
61
      points.up((index + 5) % 6).sub(points.up(index), PB)
1✔
62
    else if side == 1 then
×
63
      points.down((index + 5) % 6).sub(points.down(index), PA)
×
64
      points.down((index + 1) % 6).sub(points.down(index), PB)
65
    else
1✔
66
      points.down((index + 1) % 6).sub(points.down(index), PA)
1✔
67
      points.up(index).sub(points.down(index), PB)
68

1✔
69
    PA.cross(PB, new Vector3d())
70

1✔
71
  private def sideIndex(ray: Ray, points: PointHexagon) =
1✔
72
    if ray.toTheRight(points.down(0), points.up(0))
73
    then
×
74
      (5 to 1 by -1)
×
75
        .find(index => !ray.toTheRight(points.down(index), points.up(index)))
1✔
76
        .getOrElse(0)
77
    else (1 to 5).find(index => ray.toTheRight(points.down(index), points.up(index))).getOrElse(6) - 1
78

1✔
79
  private def actualSide(ray: Ray, points: PointHexagon, index: Int) =
1✔
80
    if ray.toTheRight(points.up(index), points.up((index + 1) % 6))
×
81
    then 0
1✔
82
    else if !ray.toTheRight(points.down(index), points.down((index + 1) % 6))
×
83
    then 1
84
    else index + 2
85

1✔
86
  private def blockTouched(
87
      blockAtCoords: BlockRelWorld => Option[BlockState],
88
      ray: Ray,
89
      hitBlockCoords: BlockRelWorld
90
  ): Boolean =
1✔
91
    blockAtCoords(hitBlockCoords) match
1✔
92
      case Some(block) if block.blockType != Block.Air =>
1✔
93
        (0 until 8).exists(side => {
1✔
94
          val boundingBox = block.blockType.bounds(block.metadata)
1✔
95
          val points = PointHexagon.fromHexBox(boundingBox, hitBlockCoords, camera)
1✔
96
          ray.intersectsPolygon(points, side)
97
        })
1✔
98
      case _ => false
99

100
class Ray(val v: Vector3d):
101

102
  /** @return
103
    *   true if the ray goes to the right of the line from `up` to `down` in a reference frame where
104
    *   `up` is directly above `down` (i.e. possibly rotated)
105
    */
1✔
106
  def toTheRight(down: Vector3d, up: Vector3d): Boolean = down.dot(up.cross(v, new Vector3d)) <= 0
107

1✔
108
  def intersectsPolygon(points: PointHexagon, side: Int): Boolean =
109
    val rightSeq =
110
      if side < 2 then
1✔
111
        for index <- 0 until 6
112
        yield
1✔
113
          val PA = if side == 0 then points.up(index) else points.down(index)
1✔
114
          val PB = if side == 0 then points.up((index + 1) % 6) else points.down((index + 1) % 6)
115

1✔
116
          this.toTheRight(PA, PB)
117
      else
1✔
UNCOV
118
        val order = Seq(0, 1, 3, 2)
×
119
        for index <- 0 until 4
120
        yield
1✔
121
          val aIdx = (order(index) % 2 + side - 2) % 6
1✔
122
          val PA = if order(index) / 2 == 0 then points.up(aIdx) else points.down(aIdx)
123

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

1✔
127
          this.toTheRight(PA, PB)
1✔
128
    allElementsSame(rightSeq)
129

1✔
130
  private def allElementsSame(seq: IndexedSeq[Boolean]) = !seq.exists(_ != seq(0))
131

132
object Ray:
1✔
133
  def fromScreen(camera: Camera, normalizedScreenCoords: Vector2fc): Option[Ray] =
134
    val coords = normalizedScreenCoords
1✔
135
    if coords.x < -1 || coords.x > 1 || coords.y < -1 || coords.y > 1
1✔
136
    then None
137
    else
1✔
138
      val coords4 = new Vector4f(coords.x, coords.y, -1, 1)
1✔
139
      coords4.mul(camera.proj.invMatrix)
1✔
140
      coords4.set(coords4.x, coords4.y, -1, 0)
1✔
141
      coords4.mul(camera.view.invMatrix)
1✔
142
      val ray = new Vector3d(coords4.x, coords4.y, coords4.z)
1✔
143
      ray.normalize()
1✔
144
      Some(Ray(ray))
145

146
object PointHexagon:
1✔
147
  def fromHexBox(hexBox: HexBox, location: BlockRelWorld, camera: Camera)(using CylinderSize): PointHexagon =
UNCOV
148
    val points =
×
149
      for v <- hexBox.vertices
1✔
150
      yield asNormalCoords(location, v, camera).toVector3d
1✔
151
    new PointHexagon(points)
152

1✔
153
  private def asNormalCoords(blockPos: BlockRelWorld, offset: CylCoords.Offset, camera: Camera)(using
154
      CylinderSize
155
  ): NormalCoords =
1✔
156
    val blockCoords = BlockCoords(blockPos).toCylCoords.offset(offset)
1✔
157
    blockCoords.toNormalCoords(CylCoords(camera.view.position))
158

159
class PointHexagon(points: Seq[Vector3d]):
1✔
160
  def up(idx: Int): Vector3d = points(idx)
1✔
161
  def down(idx: Int): Vector3d = points(idx + 6)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc