Compare commits
4 Commits
71939b9aa5
...
c4c01bedae
Author | SHA1 | Date | |
---|---|---|---|
c4c01bedae | |||
f611a0755c | |||
ae1e43a332 | |||
8c7e7e03f7 |
@ -11,9 +11,8 @@ namespace RoboidControl {
|
||||
|
||||
public BB2B(Participant owner) : base(owner) {
|
||||
this.name = "BB2B";
|
||||
this.SetMotors(new Motor(this), new Motor(this));
|
||||
this.SetDriveDimensions(0.064f, 0.128f);
|
||||
// this.wheelRadius = 0.032f;
|
||||
// this.wheelSeparation = 0.128f;
|
||||
|
||||
// Is has a touch sensor at the front left of the roboid
|
||||
touchLeft = new(this) {
|
||||
|
55
Examples/BB2B/BB2B_Encoder.cs
Normal file
55
Examples/BB2B/BB2B_Encoder.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using LinearAlgebra;
|
||||
|
||||
namespace RoboidControl {
|
||||
|
||||
// The robot is based on a differential drive
|
||||
public class BB2B_Encoder : DifferentialDrive {
|
||||
readonly DifferentialDrive drive;
|
||||
readonly TouchSensor touchLeft;
|
||||
readonly TouchSensor touchRight;
|
||||
const float speed = 180.0f; // wheel rotation speed in degrees
|
||||
|
||||
public BB2B_Encoder(Participant owner) : base(owner) {
|
||||
this.name = "BB2B";
|
||||
this.SetDriveDimensions(0.064f, 0.128f);
|
||||
|
||||
// Update the basic motors to motors with encoder
|
||||
EncoderMotor leftMotor = new(this, new RelativeEncoder()) {
|
||||
position = new Spherical(0.064f, Direction.left)
|
||||
};
|
||||
EncoderMotor rightMotor = new(this, new RelativeEncoder()) {
|
||||
position = new Spherical(0.064f, Direction.right)
|
||||
};
|
||||
this.SetMotors(leftMotor, rightMotor);
|
||||
|
||||
// Is has a touch sensor at the front left of the roboid
|
||||
touchLeft = new(this) {
|
||||
position = Spherical.Degrees(0.12f, -30, 0),
|
||||
orientation = new SwingTwist(-30, 0, 0)
|
||||
};
|
||||
touchRight = new(this) {
|
||||
position = Spherical.Degrees(0.12f, 30, 0),
|
||||
orientation = new SwingTwist(30, 0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
public override void Update(ulong currentTimeMs, bool recurse = true) {
|
||||
|
||||
// The left wheel turns forward when nothing is touched on the right side
|
||||
// and turn backward when the roboid hits something on the right
|
||||
float leftWheelVelocity = touchRight.touchedSomething ? -speed : speed;
|
||||
|
||||
// The right wheel does the same, but instead is controlled by
|
||||
// touches on the left side
|
||||
float rightWheelVelocity = touchLeft.touchedSomething ? -speed : speed;
|
||||
|
||||
// When both sides are touching something, both wheels will turn backward
|
||||
// and the roboid will move backwards
|
||||
|
||||
this.SetWheelAngularVelocity(leftWheelVelocity, rightWheelVelocity);
|
||||
|
||||
base.Update(currentTimeMs, recurse);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,71 +2,110 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RoboidControl.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// The Unity representation of a Roboid Control differential drive
|
||||
/// </summary>
|
||||
public class DifferentialDrive : Thing {
|
||||
|
||||
public Wheel leftWheel;
|
||||
public Wheel rightWheel;
|
||||
public SphereCollider casterWheel;
|
||||
|
||||
protected RoboidControl.DifferentialDrive coreDrive => core as RoboidControl.DifferentialDrive;
|
||||
/// <summary>
|
||||
/// The core differential drive
|
||||
/// </summary>
|
||||
public RoboidControl.DifferentialDrive coreDrive => core as RoboidControl.DifferentialDrive;
|
||||
|
||||
/// <summary>
|
||||
/// Create the Unity representation
|
||||
/// </summary>
|
||||
/// <param name="core">The core touch sensor</param>
|
||||
/// <param name="coreDrive">The core touch sensor</param>
|
||||
/// <returns>The Unity representation of the touch sensor</returns>
|
||||
public static DifferentialDrive Create(RoboidControl.DifferentialDrive core) {
|
||||
DifferentialDrive component = null;
|
||||
Rigidbody rb = null;
|
||||
/// This uses a 'DifferentialDrive' resource when available for the Unity representation.
|
||||
/// If this is not available, a default representation is created.
|
||||
public static DifferentialDrive Create(RoboidControl.DifferentialDrive coreDrive) {
|
||||
DifferentialDrive differentialDrive;
|
||||
GameObject prefab = (GameObject)Resources.Load("DifferentialDrive");
|
||||
if (prefab != null) {
|
||||
// Use resource prefab when available
|
||||
GameObject gameObj = Instantiate(prefab);
|
||||
component = gameObj.GetComponent<DifferentialDrive>();
|
||||
if (component != null)
|
||||
component.core = core;
|
||||
|
||||
rb = component.GetComponent<Rigidbody>();
|
||||
differentialDrive = gameObj.GetComponent<DifferentialDrive>();
|
||||
differentialDrive.Init(coreDrive);
|
||||
}
|
||||
else {
|
||||
// Fallback implementation
|
||||
GameObject gameObj = new(core.name);
|
||||
component = gameObj.AddComponent<DifferentialDrive>();
|
||||
component.Init(core);
|
||||
// Default implementation
|
||||
GameObject gameObj = new(coreDrive.name);
|
||||
differentialDrive = gameObj.AddComponent<DifferentialDrive>();
|
||||
differentialDrive.Init(coreDrive);
|
||||
|
||||
rb = gameObj.AddComponent<Rigidbody>();
|
||||
Rigidbody rb = gameObj.AddComponent<Rigidbody>();
|
||||
rb.isKinematic = false;
|
||||
rb.mass = 0.1f;
|
||||
}
|
||||
return component;
|
||||
return differentialDrive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The left wheel of the differential drive
|
||||
/// </summary>
|
||||
public Wheel leftWheel;
|
||||
/// <summary>
|
||||
/// The right wheel of the differential drive
|
||||
/// </summary>
|
||||
public Wheel rightWheel;
|
||||
/// <summary>
|
||||
/// The caster wheel to keep the differential drive horizontal
|
||||
/// </summary>
|
||||
public SphereCollider casterWheel;
|
||||
|
||||
/// <summary>
|
||||
/// The rigidbody of the differential drive
|
||||
/// </summary>
|
||||
private Rigidbody rb = null;
|
||||
|
||||
/// <summary>
|
||||
/// Start the Unity representation
|
||||
/// </summary>
|
||||
protected virtual void Start() {
|
||||
rb = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the binary event indicating a configuration change
|
||||
/// </summary>
|
||||
protected override void HandleBinary() {
|
||||
Debug.Log("Diff drive handle Binary");
|
||||
if (coreDrive.wheelRadius <= 0 || coreDrive.wheelSeparation <= 0)
|
||||
// Ignore it when the wheel radius is not set
|
||||
if (coreDrive.wheelRadius <= 0)
|
||||
return;
|
||||
|
||||
if (leftWheel == null)
|
||||
// Destroy any (generic) thing with the same id
|
||||
if (leftWheel == null) {
|
||||
Thing[] things = FindObjectsOfType<Thing>();
|
||||
foreach (Thing thing in things) {
|
||||
if (thing.core.id == coreDrive.leftWheel.id)
|
||||
Destroy(thing.gameObject);
|
||||
}
|
||||
}
|
||||
if (leftWheel == null && coreDrive.leftWheel != null)
|
||||
leftWheel = Wheel.Create(this.transform, coreDrive.leftWheel.id);
|
||||
if (leftWheel != null) {
|
||||
leftWheel.core ??= coreDrive.leftWheel;
|
||||
SphereCollider leftWheelCollider = leftWheel.GetComponent<SphereCollider>();
|
||||
leftWheelCollider.radius = coreDrive.wheelRadius;
|
||||
}
|
||||
|
||||
leftWheel.core ??= coreDrive.leftWheel;
|
||||
SphereCollider leftWheelCollider = leftWheel.GetComponent<SphereCollider>();
|
||||
leftWheelCollider.radius = coreDrive.wheelRadius;
|
||||
// leftWheelCollider.center = new Vector3(-coreDrive.wheelSeparation / 2, 0, 0);
|
||||
|
||||
if (rightWheel == null)
|
||||
// Destroy any (generic) thing with the same id
|
||||
if (rightWheel == null) {
|
||||
Thing[] things = FindObjectsOfType<Thing>();
|
||||
foreach (Thing thing in things) {
|
||||
if (thing.core.id == coreDrive.rightWheel.id)
|
||||
Destroy(thing.gameObject);
|
||||
}
|
||||
}
|
||||
if (rightWheel == null && coreDrive.rightWheel != null)
|
||||
rightWheel = Wheel.Create(this.transform, coreDrive.rightWheel.id);
|
||||
|
||||
rightWheel.core ??= coreDrive.rightWheel;
|
||||
SphereCollider rightWheelCollider = rightWheel.GetComponent<SphereCollider>();
|
||||
rightWheelCollider.radius = coreDrive.wheelRadius;
|
||||
// rightWheelCollider.center = new Vector3(coreDrive.wheelSeparation / 2, 0, 0);
|
||||
if (rightWheel != null) {
|
||||
rightWheel.core ??= coreDrive.rightWheel;
|
||||
SphereCollider rightWheelCollider = rightWheel.GetComponent<SphereCollider>();
|
||||
rightWheelCollider.radius = coreDrive.wheelRadius;
|
||||
}
|
||||
|
||||
if (casterWheel == null) {
|
||||
GameObject gameObj = new("Caster wheel");
|
||||
@ -76,9 +115,14 @@ namespace RoboidControl.Unity {
|
||||
}
|
||||
casterWheel.radius = coreDrive.wheelRadius;
|
||||
// Put it in the middle of the back
|
||||
casterWheel.center = new Vector3(0, 0, -coreDrive.wheelSeparation);
|
||||
// This code assumes that the left wheel position has Direction.left and the right wheel Direction.right...
|
||||
float wheelSeparation = coreDrive.leftWheel.position.distance + coreDrive.rightWheel.position.distance;
|
||||
casterWheel.center = new Vector3(0, 0, -wheelSeparation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Unity representation state
|
||||
/// </summary>
|
||||
protected override void FixedUpdate() {
|
||||
base.FixedUpdate();
|
||||
|
||||
@ -86,8 +130,10 @@ namespace RoboidControl.Unity {
|
||||
float leftWheelVelocity = leftWheel.rotationSpeed * (2 * Mathf.PI) * coreDrive.wheelRadius;
|
||||
float rightWheelVelocity = rightWheel.rotationSpeed * (2 * Mathf.PI) * coreDrive.wheelRadius;
|
||||
|
||||
// This code assumes that the left wheel position has Direction.left and the right wheel Direction.right...
|
||||
float wheelSeparation = coreDrive.leftWheel.position.distance + coreDrive.rightWheel.position.distance;
|
||||
float forwardSpeed = (leftWheelVelocity + rightWheelVelocity) / 2f;
|
||||
float turningSpeed = (leftWheelVelocity - rightWheelVelocity) / coreDrive.wheelSeparation;
|
||||
float turningSpeed = (leftWheelVelocity - rightWheelVelocity) / wheelSeparation;
|
||||
|
||||
// Use smoothing to emulate motor inertia
|
||||
rb.velocity = 0.9f * rb.velocity + 0.1f * forwardSpeed * transform.forward;
|
||||
|
@ -5,42 +5,45 @@ using UnityEngine;
|
||||
namespace RoboidControl.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// The Unity representation of a distance sensor
|
||||
/// The Unity representation of a Roboid Control distance sensor
|
||||
/// </summary>
|
||||
public class DistanceSensor : Thing {
|
||||
|
||||
/// <summary>
|
||||
/// The core distance sensor
|
||||
/// </summary>
|
||||
public new RoboidControl.DistanceSensor core {
|
||||
get => (RoboidControl.DistanceSensor)base.core;
|
||||
set => base.core = value;
|
||||
}
|
||||
public RoboidControl.DistanceSensor coreSensor => base.core as RoboidControl.DistanceSensor;
|
||||
|
||||
/// <summary>
|
||||
/// Start the Unity representation
|
||||
/// </summary>
|
||||
protected virtual void Start() {
|
||||
if (core == null) {
|
||||
SiteServer siteServer = FindAnyObjectByType<SiteServer>();
|
||||
SetCoreThing(new RoboidControl.DistanceSensor(siteServer.site));
|
||||
}
|
||||
|
||||
StartCoroutine(MeasureDistance());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the Unity representation of the distance sensor
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent of the core distance sensor</param>
|
||||
/// <param name="core">The core distance sensor</param>
|
||||
/// <returns>The Unity representation of the distance sensor</returns>
|
||||
public static DistanceSensor Create(RoboidControl.DistanceSensor core) {
|
||||
GameObject distanceObj = new("Distance sensor");
|
||||
DistanceSensor component = distanceObj.AddComponent<DistanceSensor>();
|
||||
if (core.parent != null && core.parent.component != null)
|
||||
distanceObj.transform.SetParent(core.parent.component.transform, false);
|
||||
|
||||
return component;
|
||||
/// This uses a 'DistanceSensor' resource when available for the Unity representation.
|
||||
/// If this is not available, a default representation is created.
|
||||
public static DistanceSensor Create(RoboidControl.DistanceSensor coreSensor) {
|
||||
DistanceSensor distanceSensor;
|
||||
GameObject prefab = (GameObject)Resources.Load("DistanceSensor");
|
||||
if (prefab != null) {
|
||||
// Use resource prefab when available
|
||||
GameObject gameObj = Instantiate(prefab);
|
||||
distanceSensor = gameObj.GetComponent<DistanceSensor>();
|
||||
distanceSensor.Init(coreSensor);
|
||||
}
|
||||
else {
|
||||
// Default implementation
|
||||
GameObject distanceObj = new(coreSensor.name);
|
||||
distanceSensor = distanceObj.AddComponent<DistanceSensor>();
|
||||
distanceSensor.Init(coreSensor);
|
||||
}
|
||||
return distanceSensor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -53,10 +56,10 @@ namespace RoboidControl.Unity {
|
||||
Thing thing = hitInfo.transform.GetComponentInParent<Thing>();
|
||||
if (thing == null) {
|
||||
// Debug.Log($"collision {hitInfo.transform.name} {hitInfo.distance}");
|
||||
core.distance = hitInfo.distance;
|
||||
coreSensor.distance = hitInfo.distance;
|
||||
}
|
||||
else
|
||||
core.distance = 0;
|
||||
coreSensor.distance = 0;
|
||||
}
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
|
@ -2,50 +2,73 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RoboidControl.Unity {
|
||||
/// <summary>
|
||||
/// The Unity representation of a Roboid Control motor
|
||||
/// </summary>
|
||||
public class Motor : Thing {
|
||||
|
||||
public float maxSpeed = 5;
|
||||
/// <summary>
|
||||
/// Create the Unity representation
|
||||
/// The core motor
|
||||
/// </summary>
|
||||
/// <param name="core">The core motor</param>
|
||||
public RoboidControl.Motor coreMotor => base.core as RoboidControl.Motor;
|
||||
|
||||
/// <summary>
|
||||
/// Create the Unity representation of the motor
|
||||
/// </summary>
|
||||
/// <param name="coreMotor">The core motor</param>
|
||||
/// <returns>The Unity representation of a motor</returns>
|
||||
public static Motor Create(RoboidControl.Motor core) {
|
||||
/// This uses a 'Motor' resource when available for the Unity representation.
|
||||
/// If this is not available, a default representation is created.
|
||||
public static Motor Create(RoboidControl.Motor coreMotor) {
|
||||
Motor motor;
|
||||
GameObject prefab = (GameObject)Resources.Load("Motor");
|
||||
if (prefab != null) {
|
||||
// Use resource prefab when available
|
||||
GameObject gameObj = Instantiate(prefab);
|
||||
Motor component = gameObj.GetComponent<Motor>();
|
||||
if (component != null)
|
||||
component.core = core;
|
||||
return component;
|
||||
motor = gameObj.GetComponent<Motor>();
|
||||
motor.Init(coreMotor);
|
||||
}
|
||||
else {
|
||||
// Fallback implementation
|
||||
GameObject gameObj = new(core.name);
|
||||
Motor component = gameObj.AddComponent<Motor>();
|
||||
component.Init(core);
|
||||
// Default implementation
|
||||
GameObject gameObj = new(coreMotor.name);
|
||||
motor = gameObj.AddComponent<Motor>();
|
||||
motor.Init(coreMotor);
|
||||
|
||||
Rigidbody rb = gameObj.AddComponent<Rigidbody>();
|
||||
rb.isKinematic = true;
|
||||
return component;
|
||||
}
|
||||
return motor;
|
||||
}
|
||||
|
||||
public float rotationSpeed = 0.0f;
|
||||
/// <summary>
|
||||
/// The maximum rotation speed of the motor in rotations per second
|
||||
/// </summary>
|
||||
public float maxSpeed = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The actual rotation speed in rotations per second
|
||||
/// </summary>
|
||||
public float rotationSpeed { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update the Unity state
|
||||
/// </summary>
|
||||
protected override void FixedUpdate() {
|
||||
base.FixedUpdate();
|
||||
// We rotate the first child of the motor, which should be the axle.
|
||||
float rotation = 360 * this.rotationSpeed * Time.fixedDeltaTime;
|
||||
if (this.transform.childCount > 0)
|
||||
this.transform.GetChild(0).Rotate(rotation, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the binary event containing the rotation speed
|
||||
/// </summary>
|
||||
protected override void HandleBinary() {
|
||||
RoboidControl.Motor coreMotor = core as RoboidControl.Motor;
|
||||
this.rotationSpeed = coreMotor.targetSpeed * maxSpeed;
|
||||
}
|
||||
|
||||
protected override void Update() {
|
||||
base.Update();
|
||||
// We rotate the first child of the motor, which should be the axle.
|
||||
float rotation = 360 * this.rotationSpeed * Time.deltaTime;
|
||||
if (this.transform.childCount > 0)
|
||||
this.transform.GetChild(0).Rotate(rotation, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -27,10 +27,12 @@ namespace RoboidControl.Unity {
|
||||
protected virtual void HandleThingEvent(RoboidControl.Participant.UpdateEvent e) {
|
||||
switch (e.thing) {
|
||||
case RoboidControl.TouchSensor coreTouchSensor:
|
||||
Debug.Log("Handle TouchSensor");
|
||||
TouchSensor touchSensor = TouchSensor.Create(coreTouchSensor);
|
||||
coreTouchSensor.component = touchSensor;
|
||||
break;
|
||||
case RoboidControl.DifferentialDrive coreDrive:
|
||||
Debug.Log("Handle Diff.Drive");
|
||||
DifferentialDrive differentialDrive = DifferentialDrive.Create(coreDrive);
|
||||
coreDrive.component = differentialDrive;
|
||||
break;
|
||||
@ -41,6 +43,7 @@ namespace RoboidControl.Unity {
|
||||
// // before we can create the wheel reliably
|
||||
// break;
|
||||
case RoboidControl.Thing coreThing:
|
||||
Debug.Log("Handle Thing");
|
||||
if (coreThing.component == null) {
|
||||
Thing thing = Thing.Create(coreThing);
|
||||
coreThing.component = thing;
|
||||
|
73
Unity/RelativeEncoder.cs
Normal file
73
Unity/RelativeEncoder.cs
Normal file
@ -0,0 +1,73 @@
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using LinearAlgebra;
|
||||
|
||||
namespace RoboidControl.Unity {
|
||||
public class RelativeEncoder : Thing {
|
||||
|
||||
/// <summary>
|
||||
/// Create the Unity representation
|
||||
/// </summary>
|
||||
/// <param name="core">The core motor</param>
|
||||
/// <returns>The Unity representation of a motor</returns>
|
||||
public static RelativeEncoder Create(RoboidControl.RelativeEncoder core) {
|
||||
GameObject prefab = (GameObject)Resources.Load("IncrementalEncoder");
|
||||
if (prefab != null) {
|
||||
// Use resource prefab when available
|
||||
GameObject gameObj = Instantiate(prefab);
|
||||
RelativeEncoder component = gameObj.GetComponent<RelativeEncoder>();
|
||||
if (component != null)
|
||||
component.core = core;
|
||||
return component;
|
||||
}
|
||||
else {
|
||||
// Fallback implementation
|
||||
GameObject gameObj = new(core.name);
|
||||
RelativeEncoder component = gameObj.AddComponent<RelativeEncoder>();
|
||||
component.Init(core);
|
||||
|
||||
Rigidbody rb = gameObj.AddComponent<Rigidbody>();
|
||||
rb.isKinematic = true;
|
||||
return component;
|
||||
}
|
||||
}
|
||||
RoboidControl.RelativeEncoder coreEncoder => this.core as RoboidControl.RelativeEncoder;
|
||||
|
||||
/// <summary>
|
||||
/// The rotation speed in degrees per second
|
||||
/// </summary>
|
||||
public float rotationSpeed = 0.0f;
|
||||
|
||||
protected override void HandleBinary() {
|
||||
this.rotationSpeed = coreEncoder.angularSpeed;
|
||||
}
|
||||
|
||||
private Quaternion lastRotation;
|
||||
protected override void Update() {
|
||||
base.Update();
|
||||
if (this.transform.childCount == 0)
|
||||
return;
|
||||
|
||||
Transform child = this.transform.GetChild(0);
|
||||
|
||||
// Get the delta rotation since last frame
|
||||
Quaternion deltaRotation = Quaternion.Inverse(lastRotation) * child.localRotation;
|
||||
|
||||
// Normalize angles to range [-180..180)
|
||||
// Note: it is not possible to track rotation speeds higher than 180 degrees per frame because of representation limitations of Quaternions
|
||||
float deltaAngle = Angle.Normalize(deltaRotation.eulerAngles.x);
|
||||
this.rotationSpeed = deltaAngle / Time.deltaTime;
|
||||
}
|
||||
|
||||
IEnumerator UpdateCore() {
|
||||
WaitForSeconds wait = new(0.1f);
|
||||
|
||||
while (true) {
|
||||
this.coreEncoder.angularSpeed = this.rotationSpeed;
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
163
Unity/Thing.cs
163
Unity/Thing.cs
@ -2,39 +2,32 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
#if GLTF
|
||||
using GLTFast;
|
||||
#endif
|
||||
|
||||
namespace RoboidControl.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// The representation of a Thing in Unity
|
||||
/// The Unity representation fo a Roboid Control Thing
|
||||
/// </summary>
|
||||
public class Thing : MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// The core C# thing
|
||||
/// </summary>
|
||||
//[field: SerializeField]
|
||||
public RoboidControl.Thing core { get; set; }
|
||||
|
||||
public SiteServer participant;
|
||||
|
||||
private string modelUrl = null;
|
||||
/// <summary>
|
||||
/// The owner of this thing
|
||||
/// </summary>
|
||||
public Participant owner;
|
||||
|
||||
/// <summary>
|
||||
/// Set the core C# thing
|
||||
/// Create a Unity representation of a Thing
|
||||
/// </summary>
|
||||
protected void SetCoreThing(RoboidControl.Thing thing) {
|
||||
core = thing;
|
||||
core.component = this;
|
||||
|
||||
SiteServer siteServer = FindAnyObjectByType<SiteServer>();
|
||||
if (siteServer == null || siteServer.site == null) {
|
||||
Debug.LogWarning("No site server found");
|
||||
return;
|
||||
}
|
||||
siteServer.site.Add(thing);
|
||||
}
|
||||
|
||||
/// <param name="core">The core of the thing</param>
|
||||
/// <returns>The created thing</returns>
|
||||
public static Thing Create(RoboidControl.Thing core) {
|
||||
// Debug.Log("Creating new Unity thing");
|
||||
GameObject gameObj = string.IsNullOrEmpty(core.name) ?
|
||||
@ -45,11 +38,17 @@ namespace RoboidControl.Unity {
|
||||
return component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Thing
|
||||
/// </summary>
|
||||
/// <param name="core">The core of the thing</param>
|
||||
/// This affects the parent and pose of the thing
|
||||
protected void Init(RoboidControl.Thing core) {
|
||||
this.core = core;
|
||||
this.core.component = this;
|
||||
this.participant = FindAnyObjectByType<SiteServer>();
|
||||
core.owner = this.participant.coreParticipant;
|
||||
this.owner = FindAnyObjectByType<SiteServer>();
|
||||
core.owner = this.owner.coreParticipant;
|
||||
|
||||
if (core.parent != null && core.parent.component != null) {
|
||||
this.transform.SetParent(core.parent.component.transform, false);
|
||||
this.transform.localPosition = Vector3.zero;
|
||||
@ -62,7 +61,7 @@ namespace RoboidControl.Unity {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Unity representation
|
||||
/// Update the Unity rendering
|
||||
/// </summary>
|
||||
protected virtual void Update() {
|
||||
if (core == null)
|
||||
@ -79,10 +78,16 @@ namespace RoboidControl.Unity {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Unity state (just calls UpdateThing)
|
||||
/// </summary>
|
||||
protected virtual void FixedUpdate() {
|
||||
UpdateThing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Unity state
|
||||
/// </summary>
|
||||
public void UpdateThing() {
|
||||
if (core == null) {
|
||||
Debug.Log($"{this} core thing is gone, self destruct in 0 seconds...");
|
||||
@ -90,37 +95,50 @@ namespace RoboidControl.Unity {
|
||||
return;
|
||||
}
|
||||
|
||||
if (core.updateQueue.TryDequeue(out RoboidControl.Thing.UpdateEvent e))
|
||||
HandleUpdateEvent(e);
|
||||
while (core.updateQueue.TryDequeue(out RoboidControl.Thing.CoreEvent e))
|
||||
HandleCoreEvent(e);
|
||||
}
|
||||
|
||||
private void HandleUpdateEvent(RoboidControl.Thing.UpdateEvent e) {
|
||||
switch (e.messageId) {
|
||||
/// <summary>
|
||||
/// Handle events from the core thing
|
||||
/// </summary>
|
||||
/// <param name="coreEvent">The core event to handle</param>
|
||||
private void HandleCoreEvent(RoboidControl.Thing.CoreEvent coreEvent) {
|
||||
switch (coreEvent.messageId) {
|
||||
case ThingMsg.id:
|
||||
Debug.Log($"{this.core.id} Handle Thing");
|
||||
if (core.parent == null)
|
||||
this.transform.SetParent(null, true);
|
||||
else if (core.parent.component != null)
|
||||
this.transform.SetParent(core.parent.component.transform, true);
|
||||
break;
|
||||
case NameMsg.Id:
|
||||
Debug.Log($"{this.core.id} Handle Name");
|
||||
this.gameObject.name = core.name;
|
||||
break;
|
||||
case ModelUrlMsg.Id:
|
||||
Debug.Log("{this.id} Handle Model URL");
|
||||
string extension = core.modelUrl[core.modelUrl.LastIndexOf(".")..];
|
||||
if (extension == ".jpg" || extension == ".png")
|
||||
StartCoroutine(LoadJPG());
|
||||
|
||||
this.modelUrl = core.modelUrl;
|
||||
else if (extension == ".gltf" || extension == ".glb")
|
||||
ProcessGltfModel(core);
|
||||
break;
|
||||
case PoseMsg.Id:
|
||||
Debug.Log($"{this.core.id} Handle Pose");
|
||||
this.HandlePose();
|
||||
break;
|
||||
case BinaryMsg.Id:
|
||||
Debug.Log($"{this.core.id} Handle Binary");
|
||||
this.HandleBinary();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load and attach a JPG sprite visualization of the thing
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IEnumerator LoadJPG() {
|
||||
UnityWebRequest request = UnityWebRequestTexture.GetTexture(core.modelUrl);
|
||||
yield return request.SendWebRequest();
|
||||
@ -148,6 +166,93 @@ namespace RoboidControl.Unity {
|
||||
}
|
||||
}
|
||||
|
||||
bool loadingModel = false;
|
||||
private async void ProcessGltfModel(RoboidControl.Thing coreThing) {
|
||||
#if GLTF
|
||||
if (!loadingModel) {
|
||||
loadingModel = true;
|
||||
|
||||
Debug.Log("Loading GLTF model from :" + coreThing.modelUrl);
|
||||
GltfImport gltfImport = new GltfImport();
|
||||
bool success = await gltfImport.Load(coreThing.modelUrl);
|
||||
if (success) {
|
||||
Transform parentTransform = this.transform;
|
||||
|
||||
Thing[] things = FindObjectsOfType<Thing>();
|
||||
Thing parentThing = null;
|
||||
foreach (Thing thing in things) {
|
||||
if (thing.core.id == coreThing.id) {
|
||||
parentTransform = thing.transform;
|
||||
parentThing = thing;
|
||||
}
|
||||
}
|
||||
await gltfImport.InstantiateMainSceneAsync(parentTransform);
|
||||
SkinnedMeshRenderer[] meshRenderers = parentTransform.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
|
||||
#if pHUMANOID4
|
||||
if (parentThing.objectType == 7) {
|
||||
HumanoidControl hc = parentThing.gameObject.GetComponent<HumanoidControl>();
|
||||
if (hc == null)
|
||||
hc = parentThing.gameObject.AddComponent<HumanoidControl>();
|
||||
|
||||
foreach (SkinnedMeshRenderer meshRenderer in meshRenderers) {
|
||||
if (meshRenderer.rootBone != null) {
|
||||
Debug.Log("Found a skinned mesh with bones");
|
||||
hc.RetrieveBonesFrom(meshRenderer.rootBone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
parentTransform.localScale = Vector3.one;
|
||||
if (meshRenderers.Length > 0) {
|
||||
foreach (SkinnedMeshRenderer meshRenderer in meshRenderers) {
|
||||
if (meshRenderer.rootBone != null) {
|
||||
Debug.Log("Found a skinned mesh with bones");
|
||||
ScanForThings(meshRenderer.rootBone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ScanForThings(parentTransform);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.transform.localScale = Vector3.one * 1;
|
||||
}
|
||||
}
|
||||
loadingModel = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ScanForThings(Transform rootTransform) {
|
||||
// Thing[] thingArray = allThings.ToArray();
|
||||
|
||||
// for (int thingIx = 0; thingIx < thingArray.Length; thingIx++) {
|
||||
// Thing thing = thingArray[thingIx];
|
||||
// GameObject foundObj = FindThingByName(thing, rootTransform);
|
||||
// if (foundObj != null && foundObj != thing.gameObject) {
|
||||
// Thing foundThing = foundObj.GetComponent<Thing>();
|
||||
// if (foundThing == null) {
|
||||
// allThings.Remove(thing);
|
||||
|
||||
// foundThing = foundObj.AddComponent<Thing>();
|
||||
// foundThing.networkId = thing.networkId;
|
||||
// foundThing.objectId = thing.objectId;
|
||||
// allThings.Add(foundThing);
|
||||
// Destroy(thing.gameObject);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle a Pose event
|
||||
/// </summary>
|
||||
/// This can update the position and/or orientation when the velocity of the thing is zero.
|
||||
/// If a velocity is not zero, the position and/or orientation update will be ignored
|
||||
protected virtual void HandlePose() {
|
||||
if (core.linearVelocity.distance == 0)
|
||||
this.transform.localPosition = core.position.ToVector3();
|
||||
@ -155,6 +260,10 @@ namespace RoboidControl.Unity {
|
||||
this.transform.localRotation = core.orientation.ToQuaternion();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a Binary event
|
||||
/// </summary>
|
||||
protected virtual void HandleBinary() { }
|
||||
|
||||
}
|
||||
|
@ -4,52 +4,36 @@ using UnityEngine;
|
||||
namespace RoboidControl.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// The Unity representation of the TouchSensor
|
||||
/// The Unity representation of a Roboid Control touch sensor
|
||||
/// </summary>
|
||||
public class TouchSensor : Thing {
|
||||
|
||||
// public SiteServer participant;
|
||||
/// <summary>
|
||||
/// The core touch sensor
|
||||
/// </summary>
|
||||
public RoboidControl.TouchSensor coreSensor {
|
||||
get => base.core as RoboidControl.TouchSensor;
|
||||
set => base.core = value;
|
||||
}
|
||||
|
||||
SphereCollider touchCollider = null;
|
||||
public RoboidControl.TouchSensor coreSensor => base.core as RoboidControl.TouchSensor;
|
||||
|
||||
/// <summary>
|
||||
/// Start the Unity represention
|
||||
/// Create the Unity representation of the touch sensor
|
||||
/// </summary>
|
||||
protected virtual void Start() {
|
||||
if (core == null) {
|
||||
participant = FindAnyObjectByType<SiteServer>();
|
||||
SetCoreThing(new RoboidControl.TouchSensor(participant.site));
|
||||
}
|
||||
touchCollider = GetComponent<SphereCollider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the Unity representation
|
||||
/// </summary>
|
||||
/// <param name="core">The core touch sensor</param>
|
||||
/// <param name="coreSensor">The core touch sensor</param>
|
||||
/// <returns>The Unity representation of the touch sensor</returns>
|
||||
public static TouchSensor Create(RoboidControl.TouchSensor core) {
|
||||
/// This uses a 'TouchSensor' resource when available for the Unity representation.
|
||||
/// If this is not available, a default representation is created.
|
||||
public static TouchSensor Create(RoboidControl.TouchSensor coreSensor) {
|
||||
TouchSensor touchSensor;
|
||||
GameObject prefab = (GameObject)Resources.Load("TouchSensor");
|
||||
if (prefab != null) {
|
||||
// Use resource prefab when available
|
||||
GameObject gameObj = Instantiate(prefab);
|
||||
TouchSensor component = gameObj.GetComponent<TouchSensor>();
|
||||
if (component != null)
|
||||
component.core = core;
|
||||
return component;
|
||||
touchSensor = gameObj.GetComponent<TouchSensor>();
|
||||
touchSensor.Init(coreSensor);
|
||||
}
|
||||
else {
|
||||
// Fallback implementation
|
||||
GameObject gameObj = new(core.name);
|
||||
TouchSensor component = gameObj.AddComponent<TouchSensor>();
|
||||
component.Init(core);
|
||||
// Default implementation
|
||||
GameObject gameObj = new(coreSensor.name);
|
||||
touchSensor = gameObj.AddComponent<TouchSensor>();
|
||||
touchSensor.Init(coreSensor);
|
||||
|
||||
Rigidbody rb = gameObj.AddComponent<Rigidbody>();
|
||||
rb.isKinematic = true;
|
||||
@ -57,30 +41,33 @@ namespace RoboidControl.Unity {
|
||||
SphereCollider collider = gameObj.AddComponent<SphereCollider>();
|
||||
collider.radius = 0.01f;
|
||||
collider.isTrigger = true;
|
||||
return component;
|
||||
}
|
||||
return touchSensor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle touch trigger collider enter event
|
||||
/// </summary>
|
||||
/// <param name="other">The collider which entered our trigger collider</param>
|
||||
private void OnTriggerEnter(Collider other) {
|
||||
// Debug.Log("Touch?");
|
||||
if (other.isTrigger) {
|
||||
// Debug.Log($" was trigger {other.name}");
|
||||
// Don't detect trigger colliders
|
||||
if (other.isTrigger)
|
||||
return;
|
||||
}
|
||||
if (this.transform.root == other.transform.root) {
|
||||
Debug.Log($" was myself {other.name}");
|
||||
// Don't touch yourself
|
||||
if (this.transform.root == other.transform.root)
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"*** {this} Touch");
|
||||
this.coreSensor.touchedSomething = true;
|
||||
this.core.updateQueue.Enqueue(new RoboidControl.Thing.UpdateEvent(BinaryMsg.Id));
|
||||
this.core.updateQueue.Enqueue(new RoboidControl.Thing.CoreEvent(BinaryMsg.Id));
|
||||
}
|
||||
/// <summary>
|
||||
/// Handl touch trigger collider exit event
|
||||
/// </summary>
|
||||
/// <param name="other">The collider which exited our trigger collider </param>
|
||||
private void OnTriggerExit(Collider other) {
|
||||
if (other.isTrigger)
|
||||
return;
|
||||
|
||||
Debug.Log($"*** {this} Touch end");
|
||||
this.coreSensor.touchedSomething = false;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RoboidControl.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// The Unity representation of a Roboid Control wheel
|
||||
/// </summary>
|
||||
public class Wheel : Motor {
|
||||
/// <summary>
|
||||
/// Create the Unity representation
|
||||
@ -23,14 +27,6 @@ namespace RoboidControl.Unity {
|
||||
GameObject gameObj = new(core.name);
|
||||
Wheel component = gameObj.AddComponent<Wheel>();
|
||||
component.Init(core);
|
||||
// component.wheelCollider = gameObj.AddComponent<WheelCollider>();
|
||||
// component.wheelCollider.mass = 0.1f;
|
||||
// component.wheelCollider.suspensionDistance = 0.01f;
|
||||
// component.wheelCollider.suspensionSpring = new JointSpring {
|
||||
// spring = 100f, // Very high spring value to make it rigid
|
||||
// damper = 10f, // Low damping (could be adjusted for slight 'bounciness')
|
||||
// targetPosition = 0.5f // Neutral position (middle of the suspension travel)
|
||||
// };
|
||||
Debug.Log("Create " + core.name);
|
||||
return component;
|
||||
}
|
||||
@ -61,14 +57,6 @@ namespace RoboidControl.Unity {
|
||||
SphereCollider collider = gameObj.AddComponent<SphereCollider>();
|
||||
collider.radius = 0.00001f;
|
||||
collider.material = slidingWheel;
|
||||
// component.wheelCollider = gameObj.AddComponent<WheelCollider>();
|
||||
// component.wheelCollider.mass = 0.1f;
|
||||
// component.wheelCollider.suspensionDistance = 0.01f;
|
||||
// component.wheelCollider.suspensionSpring = new JointSpring {
|
||||
// spring = 100f, // Very high spring value to make it rigid
|
||||
// damper = 10f, // Low damping (could be adjusted for slight 'bounciness')
|
||||
// targetPosition = 0.5f // Neutral position (middle of the suspension travel)
|
||||
// };
|
||||
Debug.Log("Create placeholder Wheel ");
|
||||
return component;
|
||||
}
|
||||
|
19
src/Thing.cs
19
src/Thing.cs
@ -26,6 +26,7 @@ namespace RoboidControl {
|
||||
public const byte ControlledMotor = 0x06;
|
||||
public const byte UncontrolledMotor = 0x07;
|
||||
public const byte Servo = 0x08;
|
||||
public const byte IncrementalEncoder = 0x19;
|
||||
// Other
|
||||
public const byte Roboid = 0x09;
|
||||
public const byte HUmanoid = 0x0A;
|
||||
@ -137,7 +138,7 @@ namespace RoboidControl {
|
||||
if (_name != value) {
|
||||
_name = value;
|
||||
nameChanged = true;
|
||||
this.updateQueue.Enqueue(new UpdateEvent(NameMsg.Id));
|
||||
this.updateQueue.Enqueue(new CoreEvent(NameMsg.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,7 +153,7 @@ namespace RoboidControl {
|
||||
set {
|
||||
if (_modelUrl != value) {
|
||||
_modelUrl = value;
|
||||
this.updateQueue.Enqueue(new UpdateEvent(ModelUrlMsg.Id));
|
||||
this.updateQueue.Enqueue(new CoreEvent(ModelUrlMsg.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,7 +188,7 @@ namespace RoboidControl {
|
||||
value.AddChild(this);
|
||||
}
|
||||
this.hierarchyChanged = true;
|
||||
this.updateQueue.Enqueue(new UpdateEvent(ThingMsg.id));
|
||||
this.updateQueue.Enqueue(new CoreEvent(ThingMsg.id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,7 +283,7 @@ namespace RoboidControl {
|
||||
if (_position != value) {
|
||||
_position = value;
|
||||
positionUpdated = true;
|
||||
updateQueue.Enqueue(new UpdateEvent(PoseMsg.Id));
|
||||
updateQueue.Enqueue(new CoreEvent(PoseMsg.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,7 +321,7 @@ namespace RoboidControl {
|
||||
if (_linearVelocity != value) {
|
||||
_linearVelocity = value;
|
||||
linearVelocityUpdated = true;
|
||||
updateQueue.Enqueue(new UpdateEvent(PoseMsg.Id));
|
||||
updateQueue.Enqueue(new CoreEvent(PoseMsg.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,7 +340,7 @@ namespace RoboidControl {
|
||||
if (_angularVelocity != value) {
|
||||
_angularVelocity = value;
|
||||
angularVelocityUpdated = true;
|
||||
updateQueue.Enqueue(new UpdateEvent(PoseMsg.Id));
|
||||
updateQueue.Enqueue(new CoreEvent(PoseMsg.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -395,13 +396,13 @@ namespace RoboidControl {
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateEvent {
|
||||
public UpdateEvent(int messageId) {
|
||||
public class CoreEvent {
|
||||
public CoreEvent(int messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
public int messageId; // see the communication messages
|
||||
};
|
||||
public ConcurrentQueue<UpdateEvent> updateQueue = new();
|
||||
public ConcurrentQueue<CoreEvent> updateQueue = new();
|
||||
|
||||
#endregion Update
|
||||
|
||||
|
@ -18,16 +18,16 @@ namespace RoboidControl {
|
||||
/// <param name="thingId">The ID of the thing, leave out or set to zero to generate an ID</param>
|
||||
/// <param name="invokeEvent">Invoke a OnNewThing event when the thing has been created</param>
|
||||
public DifferentialDrive(Participant owner, byte thingId = 0, bool invokeEvent = true) : base(owner, Type.DifferentialDrive, thingId, invokeEvent) {
|
||||
Motor leftWheel = new(this) {
|
||||
name = "Left Wheel"
|
||||
};
|
||||
Motor rightWheel = new(this) {
|
||||
name = "Right Wheel"
|
||||
};
|
||||
SetMotors(leftWheel, rightWheel);
|
||||
sendBinary = true;
|
||||
owner.Send(new BinaryMsg(owner.networkId, this));
|
||||
this.updateQueue.Enqueue(new UpdateEvent(BinaryMsg.Id));
|
||||
// Motor leftWheel = new(this) {
|
||||
// name = "Left Wheel"
|
||||
// };
|
||||
// Motor rightWheel = new(this) {
|
||||
// name = "Right Wheel"
|
||||
// };
|
||||
// SetMotors(leftWheel, rightWheel);
|
||||
// sendBinary = true;
|
||||
// owner.Send(new BinaryMsg(owner.networkId, this));
|
||||
// this.updateQueue.Enqueue(new UpdateEvent(BinaryMsg.Id));
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
@ -45,31 +45,38 @@ namespace RoboidControl {
|
||||
/// These values are used to compute the desired wheel speed from the set
|
||||
/// linear and angular velocity.
|
||||
/// @sa SetLinearVelocity SetAngularVelocity
|
||||
public void SetDriveDimensions(float wheelDiameter, float wheelSeparation) {
|
||||
public void SetDriveDimensions(float wheelDiameter, float wheelSeparation = 0) {
|
||||
this._wheelRadius = wheelDiameter > 0 ? wheelDiameter / 2 : -wheelDiameter / 2;
|
||||
this._wheelSeparation = wheelSeparation > 0 ? wheelSeparation : -wheelSeparation;
|
||||
this.rpsToMs = wheelDiameter * Angle.pi;
|
||||
|
||||
float distance = this.wheelSeparation / 2;
|
||||
if (this.leftWheel != null)
|
||||
this.leftWheel.position = new Spherical(distance, Direction.left);
|
||||
if (this.rightWheel != null)
|
||||
this.rightWheel.position = new Spherical(distance, Direction.right);
|
||||
if (wheelSeparation > 0) {
|
||||
wheelSeparation = wheelSeparation > 0 ? wheelSeparation : -wheelSeparation;
|
||||
float distance = wheelSeparation / 2;
|
||||
if (this.leftWheel != null)
|
||||
this.leftWheel.position = new Spherical(distance, Direction.left);
|
||||
if (this.rightWheel != null)
|
||||
this.rightWheel.position = new Spherical(distance, Direction.right);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Congures the motors for the wheels
|
||||
/// @param leftWheel The motor for the left wheel
|
||||
/// @param rightWheel The motor for the right wheel
|
||||
public void SetMotors(Motor leftWheel, Motor rightWheel) {
|
||||
float distance = this.wheelSeparation / 2;
|
||||
// float distance = this.wheelSeparation / 2;
|
||||
|
||||
this.leftWheel = leftWheel;
|
||||
if (this.leftWheel != null)
|
||||
this.leftWheel.position = new Spherical(distance, Direction.left);
|
||||
this.leftWheel.parent = this;
|
||||
// if (this.leftWheel != null)
|
||||
// this.leftWheel.position = new Spherical(distance, Direction.left);
|
||||
|
||||
this.rightWheel = rightWheel;
|
||||
if (this.rightWheel != null)
|
||||
this.rightWheel.position = new Spherical(distance, Direction.right);
|
||||
this.rightWheel.parent= this;
|
||||
// if (this.rightWheel != null)
|
||||
// this.rightWheel.position = new Spherical(distance, Direction.right);
|
||||
|
||||
owner.Send(new BinaryMsg(owner.networkId, this));
|
||||
this.updateQueue.Enqueue(new CoreEvent(BinaryMsg.Id));
|
||||
}
|
||||
|
||||
/// @brief Directly specify the speeds of the motors
|
||||
@ -87,6 +94,18 @@ namespace RoboidControl {
|
||||
//this.rightWheel.angularVelocity = new Spherical(speedRight, Direction.right);
|
||||
}
|
||||
}
|
||||
/// @brief Directly specify the speeds of the motors
|
||||
/// @param speedLeft The speed of the left wheel in degrees per second.
|
||||
/// Positive moves the robot in the forward direction.
|
||||
/// @param speedRight The speed of the right wheel in degrees per second.
|
||||
/// Positive moves the robot in the forward direction.
|
||||
public void SetWheelAngularVelocity(float angularSpeedLeft, float angularSpeedRight) {
|
||||
// This only works when the motor is a motor with encoder
|
||||
if (this.leftWheel is EncoderMotor leftMotor)
|
||||
leftMotor.targetAngularSpeed = angularSpeedLeft;
|
||||
if (this.rightWheel is EncoderMotor rightMotor)
|
||||
rightMotor.targetAngularSpeed = angularSpeedRight;
|
||||
}
|
||||
|
||||
/// @copydoc RoboidControl::Thing::Update(unsigned long)
|
||||
public override void Update(ulong currentMs, bool recursive = true) {
|
||||
@ -100,9 +119,10 @@ namespace RoboidControl {
|
||||
if (angularVelocity.direction.horizontal < 0)
|
||||
angularSpeed = -angularSpeed;
|
||||
|
||||
// wheel separation can be replaced by this.leftwheel.position.distance
|
||||
float speedLeft = (linearVelocity + angularSpeed * this.wheelSeparation / 2) / this.wheelRadius * Angle.Rad2Deg;
|
||||
float speedRight = (linearVelocity - angularSpeed * this.wheelSeparation / 2) / this.wheelRadius * Angle.Rad2Deg;
|
||||
// This assumes that the left wheel position has Direction.left
|
||||
// and the right wheel position has Direction.Right...
|
||||
float speedLeft = (linearVelocity + angularSpeed * this.leftWheel.position.distance) / this.wheelRadius * Angle.Rad2Deg;
|
||||
float speedRight = (linearVelocity - angularSpeed * this.rightWheel.position.distance) / this.wheelRadius * Angle.Rad2Deg;
|
||||
this.SetWheelVelocity(speedLeft, speedRight);
|
||||
}
|
||||
}
|
||||
@ -111,8 +131,8 @@ namespace RoboidControl {
|
||||
private float _wheelRadius = 0.0f;
|
||||
public float wheelRadius { get => _wheelRadius; }
|
||||
/// @brief The distance between the wheels in meters
|
||||
private float _wheelSeparation = 0.0f;
|
||||
public float wheelSeparation { get => _wheelSeparation; }
|
||||
// private float _wheelSeparation = 0.0f;
|
||||
// public float wheelSeparation { get => _wheelSeparation; }
|
||||
|
||||
/// @brief Convert revolutions per second to meters per second
|
||||
protected float rpsToMs = 1.0f;
|
||||
@ -127,12 +147,12 @@ namespace RoboidControl {
|
||||
if (!sendBinary)
|
||||
return System.Array.Empty<byte>();
|
||||
|
||||
byte[] data = new byte[6];
|
||||
byte[] data = new byte[4];
|
||||
byte ix = 0;
|
||||
data[ix++] = leftWheel.id;
|
||||
data[ix++] = rightWheel.id;
|
||||
LowLevelMessages.SendFloat16(data, ref ix, wheelRadius);
|
||||
LowLevelMessages.SendFloat16(data, ref ix, wheelSeparation);
|
||||
//LowLevelMessages.SendFloat16(data, ref ix, wheelSeparation);
|
||||
sendBinary = false;
|
||||
return data;
|
||||
}
|
||||
@ -144,8 +164,8 @@ namespace RoboidControl {
|
||||
byte rightWheelId = data[ix++];
|
||||
this.rightWheel = this.owner.Get(rightWheelId) as Motor;
|
||||
this._wheelRadius = LowLevelMessages.ReceiveFloat16(data, ref ix);
|
||||
this._wheelSeparation = LowLevelMessages.ReceiveFloat16(data, ref ix);
|
||||
this.updateQueue.Enqueue(new UpdateEvent(BinaryMsg.Id));
|
||||
//this._wheelSeparation = LowLevelMessages.ReceiveFloat16(data, ref ix);
|
||||
this.updateQueue.Enqueue(new CoreEvent(BinaryMsg.Id));
|
||||
}
|
||||
};
|
||||
|
||||
|
84
src/Things/EncoderMotor.cs
Normal file
84
src/Things/EncoderMotor.cs
Normal file
@ -0,0 +1,84 @@
|
||||
namespace RoboidControl {
|
||||
|
||||
/// @brief A motor with speed control
|
||||
/// It uses a feedback loop from an encoder to regulate the speed
|
||||
/// The speed is measured in revolutions per second.
|
||||
class EncoderMotor : Motor {
|
||||
public EncoderMotor(Thing parent, RelativeEncoder encoder) : base(parent) {
|
||||
this.encoder = encoder;
|
||||
}
|
||||
// Upgrade an existing motor with an encoder
|
||||
public EncoderMotor(Motor motor, RelativeEncoder encoder) : base(motor.parent) {
|
||||
this.motor = motor;
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
readonly RelativeEncoder encoder;
|
||||
readonly Motor motor;
|
||||
|
||||
public override float targetSpeed {
|
||||
get {
|
||||
if (this.motor != null)
|
||||
return this.motor.targetSpeed;
|
||||
else
|
||||
return base.targetSpeed;
|
||||
}
|
||||
set {
|
||||
if (this.motor != null)
|
||||
this.motor.targetSpeed = value;
|
||||
else
|
||||
base.targetSpeed = value;
|
||||
}
|
||||
}
|
||||
|
||||
private float _targetAngularSpeed; // In degrees per second
|
||||
public virtual float targetAngularSpeed {
|
||||
get => _targetAngularSpeed;
|
||||
set {
|
||||
if (value != this._targetAngularSpeed) {
|
||||
this._targetAngularSpeed = value;
|
||||
updateQueue.Enqueue(new CoreEvent(BinaryMsg.Id));
|
||||
owner.Send(new BinaryMsg(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
/// @brief Get the actual speed from the encoder
|
||||
/// @return The speed in angle per second
|
||||
public virtual float actualAngularSpee {
|
||||
get => encoder.angularSpeed;
|
||||
}
|
||||
|
||||
float pidP = 0.1F;
|
||||
float pidD = 0.0F;
|
||||
float pidI = 0.0F;
|
||||
|
||||
public override void Update(ulong currentTimeMs, bool recurse = false) {
|
||||
float actualSpeed = this.encoder.angularSpeed;
|
||||
// Simplified rotation direction, shouldbe improved
|
||||
// This goes wrong when the target speed is inverted and the motor axcle is still turning in the old direction
|
||||
if (this.targetAngularSpeed < 0)
|
||||
actualSpeed = -actualSpeed;
|
||||
|
||||
float deltaTime = (currentTimeMs - this.lastUpdateTime) / 1000.0f;
|
||||
|
||||
float error = this.targetAngularSpeed - actualSpeed;
|
||||
float errorChange = (error - lastError) * deltaTime;
|
||||
|
||||
float delta = error * pidP + errorChange * pidD;
|
||||
|
||||
// Update the motor speed
|
||||
if (motor != null)
|
||||
motor.targetSpeed += delta;
|
||||
else
|
||||
base.targetSpeed += delta;
|
||||
this.lastUpdateTime = currentTimeMs;
|
||||
}
|
||||
|
||||
ulong lastUpdateTime = 0;
|
||||
float lastError = 0;
|
||||
|
||||
// enum Direction { Forward = 1, Reverse = -1 };
|
||||
// Direction rotationDirection;
|
||||
};
|
||||
|
||||
} // namespace RoboidControl
|
@ -16,16 +16,16 @@ namespace RoboidControl {
|
||||
|
||||
protected float currentTargetSpeed = 0;
|
||||
|
||||
private float _targetSpeed;
|
||||
private float _targetSpeed = 0;
|
||||
/// <summary>
|
||||
/// The speed between -1 (full reverse), 0 (stop) and 1 (full forward)
|
||||
/// </summary>
|
||||
public float targetSpeed {
|
||||
public virtual float targetSpeed {
|
||||
get => _targetSpeed;
|
||||
set {
|
||||
if (value != _targetSpeed) {
|
||||
_targetSpeed = Float.Clamp(value, -1, 1);
|
||||
updateQueue.Enqueue(new UpdateEvent(BinaryMsg.Id));
|
||||
updateQueue.Enqueue(new CoreEvent(BinaryMsg.Id));
|
||||
owner.Send(new BinaryMsg(this));
|
||||
}
|
||||
}
|
||||
|
40
src/Things/RelativeEncoder.cs
Normal file
40
src/Things/RelativeEncoder.cs
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
namespace RoboidControl {
|
||||
|
||||
/// @brief An Incremental Encoder measures the rotations of an axle using a rotary
|
||||
/// sensor. Some encoders are able to detect direction, while others can not.
|
||||
public class RelativeEncoder : Thing {
|
||||
/// @brief Creates a sensor which measures distance from pulses
|
||||
/// @param pulsesPerRevolution The number of pulse edges which make a
|
||||
/// full rotation
|
||||
/// @param distancePerRevolution The distance a wheel travels per full
|
||||
/// rotation
|
||||
public RelativeEncoder(bool invokeEvent = true) : base(Type.IncrementalEncoder, invokeEvent) { }
|
||||
|
||||
protected float _rotationSpeed = 0;
|
||||
/// @brief Get the rotation speed since the previous call
|
||||
/// @param currentTimeMs The clock time in milliseconds
|
||||
/// @return The speed in rotations per second
|
||||
public virtual float angularSpeed {
|
||||
get => _rotationSpeed;
|
||||
set {
|
||||
if (_rotationSpeed != value) {
|
||||
_rotationSpeed = value;
|
||||
this.owner.Send(new BinaryMsg(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] GenerateBinary() {
|
||||
byte[] data = new byte[1];
|
||||
byte ix = 0;
|
||||
data[ix++] = (byte)(this._rotationSpeed * 127);
|
||||
return data;
|
||||
}
|
||||
public override void ProcessBinary(byte[] data) {
|
||||
this._rotationSpeed = (float)data[0] / 127;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace RoboidControl
|
Loading…
x
Reference in New Issue
Block a user