Sensing the world
Rangefinders, light sensors, line followers and cameras are all built from three general tools. None of them knows what device it serves.
| Tool | Where | Answers | Call it from |
|---|---|---|---|
Ray: Cast and CastTo |
Any rendering node | What is along a line: hit or not, distance, normal, angle, and the struck surface’s own fields | PhysicsUpdate |
Field: SampleEnvironment and SampleField |
Circuit logic (at the part), or any rendering node (at that node, facing a direction) | A named value at a point, such as "Lux" or "TemperatureC", from environment zones and emitters |
PreStep (circuit), Update (component) |
Eye: ImageSensorWrapper |
An image sensor marker | A small rendered picture of what the node faces | Update, one frame after Capture() |
A field is what a sensor measures. An eye is what a camera photographs. A photoresistor reads the Lux field and doesn’t change with the viewport’s lighting toggle. A camera module sees the lit scene.
Marking surfaces
Section titled “Marking surfaces”A ray reports the surface fields of the body it strikes: named numbers such as IrReflectance: 0.05 or AcousticReflectance: 0.1. The names are yours to choose. Your sensor reads one with hit.SampleSurface("IrReflectance", fallback), and gets the fallback when the surface doesn’t have that field, so keep a sensible default in a property.
Surface fields are part of a body’s settings. Select the body, and in the inspector’s Sensing section type them into Surface Fields, one per line, such as IrReflectance: 0.8.
- For every copy of a part, set them in the component editor, on the main body under Physics or on a mesh’s part body. A black chassis can carry
IrReflectance: 0.05wherever it goes. - For one copy in a circuit, set them on the part’s Rigidbody row in the hierarchy. This is how you mark the bench: a flat prop with
IrReflectance: 0.8makes a white track, and thin props on it withIrReflectance: 0.05make the line. An edit takes effect at once, even while the simulation plays.
The floor carries no fields, and neither does a part without a body. Rays only see bodies, which exist while the simulation plays, and a ray never strikes the part casting it.
Examples
Section titled “Examples”// Ultrasonic ranger: the "Transducer" node looks along -Z. Centimetres to the circuit.public override void PhysicsUpdate(double delta){ var hit = Rendering.Transducer.Cast(Vector3.Forward, 4.0); Circuit.UserData[0] = hit.Hit ? hit.Distance * 100.0 : -1.0; // -1 = no echo}// Reflectance sensor: how much infrared the surface under the emitter sends back.public override void PhysicsUpdate(double delta){ var hit = Rendering.Emitter.Cast(Vector3.Down, 0.05); Circuit.UserData[0] = hit.SampleSurface("IrReflectance", Properties.Reflectance) * hit.Incidence;}// A lamp: a FieldEmitter node named "Emitter" with FieldName "Lux". Brightness follows current.public override void Update(double delta){ Rendering.Emitter.Intensity = Properties.LuxPerAmpAt100mm * Circuit.GetCurrent(0);}// Optical line follower: an 8 x 1 image sensor named "Eye" looking down. Left and right halves to the circuit.private int _seen;
public override void Update(double delta){ var eye = Rendering.Eye; if (eye.FrameVersion != _seen) { _seen = eye.FrameVersion; Circuit.UserData[0] = eye.MeanLuminance(0, 0, eye.Width / 2, eye.Height); Circuit.UserData[1] = eye.MeanLuminance(eye.Width / 2, 0, eye.Width, eye.Height); } eye.Capture();}In circuit logic, fall back to the matching property when nothing is sensed, so the part still works on an empty bench: SampleEnvironment("Lux", Properties.Lux).
- A ray is one query per call. Cast at the device’s own rate (an HC-SR04 pings every few milliseconds), not every step, especially for a cone of rays.
- A capture is a small render plus a GPU readback, with at most 8 per frame across all sensors. Capture only when you need a picture.

