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

Scala-Robotics-Simulator / PPS-22-srs / #316

20 Aug 2025 10:04AM UTC coverage: 71.169% (+9.7%) from 61.478%
#316

push

github

srs-mate
refactor(sensor): remove distance, remove range from light sensor

33 of 33 new or added lines in 2 files covered. (100.0%)

11 existing lines in 3 files now uncovered.

1096 of 1540 relevant lines covered (71.17%)

10.08 hits per line

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

95.12
/src/main/scala/io/github/srs/model/entity/dynamicentity/sensor/Sensor.scala
1
package io.github.srs.model.entity.dynamicentity.sensor
2

3
import cats.syntax.all.*
4
import io.github.srs.model.PositiveDouble
5
import io.github.srs.model.entity.dynamicentity.{ DynamicEntity, Robot }
6
import io.github.srs.model.entity.{ Orientation, Point2D }
7
import io.github.srs.model.environment.Environment
8
import io.github.srs.model.validation.Validation
9
import io.github.srs.utils.Ray.intersectRay
10
import io.github.srs.utils.SimulationDefaults.DynamicEntity.Sensor.ProximitySensor as ProximitySensorDefaults
11
import io.github.srs.model.illumination.LightMap
12
import io.github.srs.model.illumination.engine.SquidLibFovEngine
13
import io.github.srs.model.illumination.model.ScaleFactor
14
import cats.effect.kernel.Sync
15
import io.github.srs.model.entity.ShapeType
16

17
/**
18
 * Represents the range of a sensor.
19
 */
20
type Range = Double
UNCOV
21

×
22
/**
23
 * Represents a sensor that can sense the environment for a dynamic entity.
24
 * @tparam Entity
25
 *   the type of dynamic entity that the sensor can act upon.
26
 * @tparam Env
27
 *   the type of environment in which the sensor operates.
28
 */
29
trait Sensor[-Entity <: DynamicEntity, -Env <: Environment]:
30
  /**
8✔
31
   * The type of data that the sensor returns. This type can vary based on the specific sensor implementation.
32
   */
33
  type Data
34

35
  /**
36
   * The offset orientation of the sensor relative to the entity's orientation.
37
   * @return
38
   *   the orientation offset of the sensor.
39
   */
40
  def offset: Orientation
41

42
  /**
43
   * Senses the environment for the given entity and returns the data collected by the sensor.
44
   * @param entity
45
   *   the dynamic entity that the sensor is attached to.
46
   * @param env
47
   *   the environment in which the sensor operates.
48
   * @tparam F
49
   *   the effect type in which the sensing operation is performed.
50
   * @return
51
   *   a monadic effect containing the data sensed by the sensor.
52
   */
53
  def sense[F[_]: Sync](entity: Entity, env: Env): F[Data]
54

55
  private[sensor] def direction(entity: Entity): Point2D =
56
    val globalOrientation = entity.orientation.toRadians + offset.toRadians
4✔
57
    (math.cos(globalOrientation), -math.sin(globalOrientation))
24✔
58

20✔
59
  private[sensor] def origin(entity: Entity): Point2D =
60
    import Point2D.*
4✔
61
    val distance = entity.shape match
62
      case ShapeType.Circle(radius) => radius
7✔
63
      case ShapeType.Rectangle(width, height) => math.min(width, height)
19✔
64
    val origin = entity.position + direction(entity) * distance
1✔
65
    origin
33✔
66
end Sensor
2✔
67

68
/**
69
 * Represents a reading from a sensor. This case class encapsulates the sensor and the value it has sensed.
70
 * @param sensor
71
 *   the sensor that has taken the reading.
72
 * @param value
73
 *   the value sensed by the sensor.
74
 * @tparam S
75
 *   the type of sensor, which is a subtype of [[Sensor]].
76
 * @tparam A
77
 *   the type of data sensed by the sensor.
78
 */
79
final case class SensorReading[S <: Sensor[?, ?], A](sensor: S, value: A)
80

68✔
81
/**
82
 * A collection of sensor readings. This type is used to represent multiple sensor readings from a dynamic entity. It is
83
 * a vector of [[SensorReading]] instances, allowing for efficient access and manipulation of sensor data.
84
 */
85
type SensorReadings = Vector[SensorReading[? <: Sensor[?, ?], ?]]
86

87
/**
88
 * A proximity sensor that can sense the distance to other entities in the environment. It calculates the distance to
89
 * the nearest entity within its range and returns a normalized value. The value is normalized to a range between 0.0
90
 * (closest) and 1.0 (farthest).
91
 * @param offset
92
 *   the offset orientation of the sensor relative to the entity's orientation.
93
 * @param distance
94
 *   the distance from the center of the entity to the sensor.
95
 * @param range
96
 *   the range of the sensor, which defines how far it can sense.
97
 * @tparam Entity
98
 *   the type of dynamic entity that the sensor can act upon.
99
 * @tparam Env
100
 *   the type of environment in which the sensor operates.
101
 */
102
final case class ProximitySensor[Entity <: DynamicEntity, Env <: Environment](
103
    override val offset: Orientation = Orientation(ProximitySensorDefaults.defaultOffset),
86✔
104
    val range: Range = ProximitySensorDefaults.defaultRange,
24✔
105
) extends Sensor[Entity, Env]:
16✔
106

107
  override type Data = Double
108

109
  private def rayEnd(entity: Entity): Point2D =
110
    import Point2D.*
4✔
111
    origin(entity) + direction(entity) * range
112

35✔
113
  override def sense[F[_]: Sync](entity: Entity, env: Env): F[Data] =
114
    Sync[F].pure:
4✔
115
      val o = origin(entity)
10✔
116
      val end = rayEnd(entity)
12✔
117

8✔
118
      val distances = env.entities
119
        .filter(!_.equals(entity))
5✔
120
        .flatMap(intersectRay(_, o, end))
7✔
121
        .filter(_ <= range)
8✔
122

8✔
123
      distances.minOption.map(_ / range).getOrElse(1.0)
124

29✔
125
end ProximitySensor
126

127
/**
128
 * A light sensor that senses the light intensity in the environment.
129
 * @param offset
130
 *   the offset orientation of the sensor relative to the entity's orientation.
131
 * @param distance
132
 *   the distance from the center of the entity to the sensor.
133
 * @param range
134
 *   the range of the sensor, which defines how far it can sense.
135
 * @tparam Entity
136
 *   the type of dynamic entity that the sensor can act upon.
137
 * @tparam Env
138
 *   the type of environment in which the sensor operates.
139
 */
140
final case class LightSensor[Entity <: DynamicEntity, Env <: Environment](
141
    offset: Orientation = Orientation(ProximitySensorDefaults.defaultOffset),
32✔
142
) extends Sensor[Entity, Env]:
6✔
143

144
  override type Data = Double
145

146
  /**
147
   * Senses the light intensity at the position of the sensor in the environment. It uses a cached light map to compute
148
   * the field of light and samples it at the sensor's position.
149
   * @param entity
150
   *   the dynamic entity that the sensor is attached to.
151
   * @param env
152
   *   the environment in which the sensor operates.
153
   * @return
154
   *   a monadic effect containing the light intensity sensed by the sensor.
155
   */
156
  override def sense[F[_]: Sync](entity: Entity, env: Env): F[Data] =
157
    val o = origin(entity)
4✔
158
    for
8✔
159
      lightMap <- LightMap.cached[F](SquidLibFovEngine, ScaleFactor.default)
1✔
160
      field <- lightMap.computeField(env = env, includeDynamic = true)
39✔
161
    yield field.sampleAtWorld(o)(using ScaleFactor.default)
162

3✔
163
end LightSensor
164

165
object Sensor:
UNCOV
166

×
167
  extension [E <: DynamicEntity, Env <: Environment](s: ProximitySensor[E, Env])
168

5✔
169
    /**
170
     * Validates the properties of a sensor.
171
     */
172
    def validate: Validation[ProximitySensor[E, Env]] =
173
      for _ <- PositiveDouble(s.range).validate
4✔
174
      yield s
21✔
175

3✔
176
  extension (r: Robot)
177

178
    /**
179
     * Senses all sensors of the robot in the given environment.
180
     * @param env
181
     *   the environment in which to sense.
182
     * @return
183
     *   a vector of sensor readings.
184
     */
185
    def senseAll[F[_]: Sync](env: Environment): F[SensorReadings] =
186
      r.sensors.traverse: sensor =>
4✔
187
        sensor.sense(r, env).map(reading => SensorReading(sensor, reading))
17✔
188
end Sensor
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

© 2026 Coveralls, Inc