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

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

20 Oct 2025 06:09PM UTC coverage: 78.302% (+0.4%) from 77.925%
#716

push

github

srs-mate
refactor: correct doc for DifferentialKinematics

1393 of 1779 relevant lines covered (78.3%)

8.73 hits per line

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

97.44
/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

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

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

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

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

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

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

67
/**
68
 * Represents a reading from a sensor. This case class encapsulates the sensor and the value it has sensed.
69
 *
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

92✔
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 collection of proximity sensor readings. This type is a specialized version of [[SensorReadings]], specifically for
89
 * proximity sensors. It is a vector of [[SensorReading]] instances where the sensor is a [[ProximitySensor]] and the
90
 * data is of type `Double`.
91
 */
92
type ProximityReadings = Vector[SensorReading[ProximitySensor[?, ?], ProximitySensor[?, ?]#Data]]
93

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

101
object SensorReadings:
102

3✔
103
  extension (readings: SensorReadings)
104

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

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

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

145
end SensorReadings
146

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

166
  override type Data = Double
167

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

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

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

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

12✔
184
end ProximitySensor
185

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

200
  override type Data = Double
201

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

9✔
218
end LightSensor
219

220
object Sensor:
221

×
222
  extension (r: Robot)
223

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