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

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

21 Oct 2025 01:07PM UTC coverage: 76.131% (-2.2%) from 78.302%
#734

push

github

srs-mate
feat: add truncation model

9 of 13 new or added lines in 5 files covered. (69.23%)

74 existing lines in 12 files now uncovered.

1515 of 1990 relevant lines covered (76.13%)

8.48 hits per line

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

97.56
/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.Monad
4
import cats.syntax.all.*
5
import io.github.srs.model.entity.dynamicentity.DynamicEntity
6
import io.github.srs.model.entity.dynamicentity.robot.Robot
7
import io.github.srs.model.entity.{ Orientation, Point2D, ShapeType }
8
import io.github.srs.model.environment.Environment
9
import io.github.srs.model.illumination.model.ScaleFactor
10
import io.github.srs.utils.Ray.intersectRay
11
import io.github.srs.utils.SimulationDefaults.DynamicEntity.Sensor.ProximitySensor as ProximitySensorDefaults
12
import io.github.srs.model.entity.dynamicentity.agent.Agent
13

14
/**
15
 * Represents the range of a sensor.
16
 */
17
type Range = Double
18

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

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

41
  /**
42
   * Senses the environment for the given entity and returns the data collected by the sensor.
43
   *
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[_]: Monad](entity: Entity, env: Env): F[Data]
54

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

11✔
59
  private[sensor] def origin(entity: Entity): Point2D =
60
    import Point2D.*
61
    val distance = entity.shape match
62
      case ShapeType.Circle(radius) => radius
3✔
63
      case ShapeType.Rectangle(width, height) => math.min(width, height)
17✔
64
    val origin = entity.position + direction(entity) * distance
1✔
65
    origin
11✔
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
 *
71
 * @param sensor
72
 *   the sensor that has taken the reading.
73
 * @param value
74
 *   the value sensed by the sensor.
75
 * @tparam S
76
 *   the type of sensor, which is a subtype of [[Sensor]].
77
 * @tparam A
78
 *   the type of data sensed by the sensor.
79
 */
80
final case class SensorReading[S <: Sensor[?, ?], A](sensor: S, value: A)
81

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

88
/**
89
 * A collection of proximity sensor readings. This type is a specialized version of [[SensorReadings]], specifically for
90
 * proximity sensors. It is a vector of [[SensorReading]] instances where the sensor is a [[ProximitySensor]] and the
91
 * data is of type `Double`.
92
 */
93
type ProximityReadings = Vector[SensorReading[ProximitySensor[?, ?], ProximitySensor[?, ?]#Data]]
94

95
/**
96
 * A collection of light sensor readings. This type is a specialized version of [[SensorReadings]], specifically for
97
 * light sensors. It is a vector of [[SensorReading]] instances where the sensor is a [[LightSensor]] and the data is of
98
 * type `Double`.
99
 */
100
type LightReadings = Vector[SensorReading[LightSensor[?, ?], LightSensor[?, ?]#Data]]
101

102
object SensorReadings:
103

3✔
104
  extension (readings: SensorReadings)
105

5✔
106
    /**
107
     * Pretty prints the sensor readings in a human-readable format. Each reading is formatted based on the type of
108
     * sensor
109
     *
110
     * @return
111
     *   a vector of strings representing the pretty-printed sensor readings.
112
     */
113
    def prettyPrint: Vector[String] =
114
      readings
115
        .map(r =>
1✔
116
          r.sensor match
4✔
117
            case ProximitySensor(offset, range) =>
118
              s"Proximity (offset: ${offset.degrees}°, range: $range m) -> ${r.value}"
119
            case LightSensor(offset) => s"Light (offset: ${offset.degrees}°) -> ${r.value}"
120
            case other => s"${other.getClass.getSimpleName} -> ${r.value}",
121
        )
122

123
    /**
124
     * Filters the sensor readings to include only those from proximity sensors.
125
     *
126
     * @return
127
     *   a vector of sensor readings from proximity sensors.
128
     */
129
    def proximityReadings: ProximityReadings =
130
      readings.collect { case _ @SensorReading(s: ProximitySensor[?, ?], v: Double) =>
131
        SensorReading(s, v)
37✔
132
      }
16✔
133

134
    /**
135
     * Filters the sensor readings to include only those from light sensors.
136
     *
137
     * @return
138
     *   a vector of sensor readings from light sensors.
139
     */
140
    def lightReadings: LightReadings =
141
      readings.collect { case _ @SensorReading(s: LightSensor[?, ?], v: Double) =>
142
        SensorReading(s, v)
37✔
143
      }
16✔
144
  end extension
145

146
end SensorReadings
147

148
/**
149
 * A proximity sensor that can sense the distance to other entities in the environment. It calculates the distance to
150
 * the nearest entity within its range and returns a normalized value. The value is normalized to a range between 0.0
151
 * (closest) and 1.0 (farthest).
152
 *
153
 * @param offset
154
 *   the offset orientation of the sensor relative to the entity's orientation.
155
 * @param range
156
 *   the range of the sensor, which defines how far it can sense.
157
 * @tparam Entity
158
 *   the type of dynamic entity that the sensor can act upon.
159
 * @tparam Env
160
 *   the type of environment in which the sensor operates.
161
 */
162
final case class ProximitySensor[Entity <: DynamicEntity, Env <: Environment](
163
    override val offset: Orientation = Orientation(ProximitySensorDefaults.DefaultOffset),
105✔
164
    range: Range = ProximitySensorDefaults.DefaultRange,
14✔
165
) extends Sensor[Entity, Env]:
12✔
166

167
  override type Data = Double
168

169
  private def rayEnd(entity: Entity): Point2D =
170
    import Point2D.*
171
    origin(entity) + direction(entity) * range
172

13✔
173
  override def sense[F[_]: Monad](entity: Entity, env: Env): F[Data] =
174
    Monad[F].pure:
175
      val o = origin(entity)
3✔
176
      val end = rayEnd(entity)
4✔
177

4✔
178
      val distances = env.entities
179
        .filter(!_.equals(entity))
2✔
180
        .flatMap(intersectRay(_, o, end))
3✔
181
        .filter(_ <= range)
4✔
182

4✔
183
      distances.minOption.map(_ / range).getOrElse(1.0)
184

12✔
185
end ProximitySensor
186

187
/**
188
 * A light sensor that senses the light intensity in the environment.
189
 *
190
 * @param offset
191
 *   the offset orientation of the sensor relative to the entity's orientation.
192
 * @tparam Entity
193
 *   the type of dynamic entity that the sensor can act upon.
194
 * @tparam Env
195
 *   the type of environment in which the sensor operates.
196
 */
197
final case class LightSensor[Entity <: DynamicEntity, Env <: Environment](
198
    offset: Orientation = Orientation(ProximitySensorDefaults.DefaultOffset),
62✔
199
) extends Sensor[Entity, Env]:
6✔
200

201
  override type Data = Double
202

203
  /**
204
   * Senses the light intensity at the position of the sensor in the environment. It uses a cached light map to compute
205
   * the field of light and samples it at the sensor's position.
206
   *
207
   * @param entity
208
   *   the dynamic entity that the sensor is attached to.
209
   * @param env
210
   *   the environment in which the sensor operates.
211
   * @return
212
   *   a monadic effect containing the light intensity sensed by the sensor.
213
   */
214
  override def sense[F[_]: Monad](entity: Entity, env: Env): F[Data] =
215
    Monad[F].pure:
216
      val o = origin(entity)
3✔
217
      env.lightField.illuminationAt(o)(using ScaleFactor.default)
4✔
218

9✔
219
end LightSensor
220

221
object Sensor:
UNCOV
222

×
223
  extension (r: Robot)
224

5✔
225
    /**
226
     * Senses all sensors of the robot in the given environment.
227
     *
228
     * @param env
229
     *   the environment in which to sense.
230
     * @return
231
     *   a vector of sensor readings.
232
     */
233
    def senseAll[F[_]: Monad](env: Environment): F[SensorReadings] =
234
      r.sensors.traverse: sensor =>
235
        sensor.sense(r, env).map(reading => SensorReading(sensor, reading))
10✔
236

3✔
237
  extension (a: Agent)
238

239
    /**
240
     * Senses all sensors of the agent in the given environment.
241
     *
242
     * @param env
243
     *   the environment in which to sense.
244
     * @return
245
     *   a vector of sensor readings.
246
     */
247
    def senseAll[F[_]: Monad](env: Environment): F[SensorReadings] =
248
      a.sensors.traverse: sensor =>
249
        sensor.sense(a, env).map(reading => SensorReading(sensor, reading))
10✔
250
end Sensor
3✔
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