Open csabau opened 2 years ago
I've got a Skeleton with the latest update!
I've moved the DrawLine
class in the BodyTrack2D
file so that I can take advantage of how the data is being processed there, as the actual values CGPoints values are in that file. Then I noticed I don't need the actual CGPoints themselves as there is a function that already does something based on the TwoDBodyJoint
array of joints - angleFrom2Joints. This takes in 2 joints of type TwoDBodyJoint
and although it does call private func angleBetween2Points
to work out the angles, I don't need that part.
So I've replicated that function and modified to suit my needs - taking 2 joints as arguments and using the DrawLine
class to draw a line between them. Called this function lineBetween2Joints. I've simply passed on the screen position of the joints from the argument into my DrawLine
class.
I then called this in the ARSUIView2D
file within the ARSessionDelegate to make sure it always updates with the latest position between the joints so that the line is accurate. I did however encounter the issue that the lines kept on drawing on the screen and never removing, filling my screen with lines.
The way the circles are attached are by providing an updated location each view's location via updateTrackedViews, because the views were stored in trackedViews
. That makes it easier to reposition the centre of a UIView.
In lines are different because I don't care about the centre, but the two joints it's connecting. So I've created a similar variable to track every bone - trackedBones. With that I was able to create a removeBone function that I call in the ARSUIView2D
file, every frame, before drawing the line. This leaves only the most up to date line on the screen and gives the impression that the line is moving with the joints.
I then created a dictionary for all the lines I wanted to create so that this whole process is a bit more automated. This bonesToSHow contains a dictionary of String keys
and TwoDBodyJoint array of values
. The key
allows me to not only identify what bone needs to be created, but to assign it a name in the trackedBones dictionary
as well, which allows me to remove it. The array of values
makes it easy to pass the values to the DrawLine class
.
Now I have the feeling you might think that this is a quite an unorthodox solution to the problem...but it does the job. Do you think I should be worrying about improvements for performance? Am I drawing and removing too many UIViews?
I know the code needs tidying up, but I feel like I've made MASSIVE progress today.
Do you think I should be worrying about improvements for performance? Am I drawing and removing too many UIViews?
Whatever you can get to work is as good a solution as any other. It is a lot harder to improve nothing than it is something.
What you might be taking the wrong approach with is the drawing and inheritance of UIView. It would probably be worthwhile quickly casting your eye over the docs. For instance, you'll want to do your drawing in the draw
method. You can then create some function to both update the start and end points and also update redraw.
The core animation layer part might actually throw things a little:
typically you'd want to add the circles and the paths, otherwise you'll get some weird looking animation.
This is pretty thrown together but here are two potential ways, on keeping the other discarding the CALayer
In both cases all you'd need to do is trackedBones[bone]?.update(newStartPoint, newEndPoint)
instead of removing and re-adding the bones
//---------------------------- Add Line between points------------
class DrawLine: UIView{
//--------------------------------------------------------------------------
var path: UIBezierPath!
var strokeColour: CGColor = #colorLiteral(red: 0.670588235294118, green: 0.898039215686275, blue: 0.12156862745098, alpha: 1)
var fillColour: CGColor = #colorLiteral(red: 0.250980392156863, green: 0.250980392156863, blue: 0.250980392156863, alpha: 0.0)
var startPoint: CGPoint?
var endPoint: CGPoint?
//--------------------------------------------------------------------------
init(frame: CGRect, start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
super.init(frame: frame)
}
//--------------------------------------------------------------------------
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//--------------------------------------------------------------------------
/// create a simple straight line
/// - Parameters:
/// - start:
/// - end:
func createLine(start: CGPoint, end: CGPoint) {
path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
}
//--------------------------------------------------------------------------
/// Create the CAShapeLayer Object because it's more versatile and can define stroke width.
/// We assign the path of the BezierPath from above
func simpleShapeLayer() {
self.createLine(start: startPoint!, end: endPoint!)
let shapeLayer = CAShapeLayer()
shapeLayer.path = self.path.cgPath
shapeLayer.fillColor = fillColour
shapeLayer.strokeColor = strokeColour
shapeLayer.lineWidth = 4.0
self.layer.addSublayer(shapeLayer)
}
//--------------------------------------------------------------------------
/// This is where any drawing _should_ go
/// - Parameter rect: this is the bounding rectangle of the view
override func draw(_ rect: CGRect) {
simpleShapeLayer()
}
//--------------------------------------------------------------------------
/// update the points of the line and set the view to redraw
/// - Parameters:
/// - start: new start point
/// - end: new end point
func update(start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
setNeedsDisplay();
}
//--------------------------------------------------------------------------
}
class DrawLine: UIView{
//--------------------------------------------------------------------------
var path: UIBezierPath!
var strokeColour: CGColor = #colorLiteral(red: 0.670588235294118, green: 0.898039215686275, blue: 0.12156862745098, alpha: 1)
var fillColour: CGColor = #colorLiteral(red: 0.250980392156863, green: 0.250980392156863, blue: 0.250980392156863, alpha: 0.0)
var startPoint: CGPoint!
var endPoint: CGPoint!
//--------------------------------------------------------------------------
init(frame: CGRect, start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
super.init(frame: frame)
}
//--------------------------------------------------------------------------
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//--------------------------------------------------------------------------
/// This is where any drawing _should_ go
/// - Parameter rect: this is the bounding rectangle of the view
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.move(to: self.startPoint)
context?.addLine(to: self.endPoint)
context?.setStrokeColor(strokeColour)
context?.setFillColor(fillColour)
context?.setLineWidth(2)
context?.strokePath()
}
//--------------------------------------------------------------------------
/// update the points of the line and set the view to redraw
/// - Parameters:
/// - start: new start point
/// - end: new end point
func update(start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
setNeedsDisplay();
}
//--------------------------------------------------------------------------
}
Realised the above might also duplicate lines depending on the relationship to the ARView
In which case, removing and re-adding a view is probably the easiest option. You could make a little more self contained by creating 2 classes
LineView
which simply draws a lineBoneView
which adds a LineView
as a subview
and provides an update
function to deal with clearing and redrawing the view.Ultimately you'll want to end up with something like a Skeleton2DView
object that collects this together
class LineView: UIView{
//--------------------------------------------------------------------------
var path: UIBezierPath!
var strokeColour: CGColor = #colorLiteral(red: 0.670588235294118, green: 0.898039215686275, blue: 0.12156862745098, alpha: 1)
var fillColour: CGColor = #colorLiteral(red: 0.250980392156863, green: 0.250980392156863, blue: 0.250980392156863, alpha: 0.0)
var startPoint: CGPoint!
var endPoint: CGPoint!
//--------------------------------------------------------------------------
init(frame: CGRect, start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
super.init(frame: frame)
}
//--------------------------------------------------------------------------
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//--------------------------------------------------------------------------
/// This is where any drawing _should_ go
/// - Parameter rect: this is the bounding rectangle of the view
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.move(to: self.startPoint)
context?.addLine(to: self.endPoint)
context?.setStrokeColor(strokeColour)
context?.setFillColor(fillColour)
context?.setLineWidth(2)
context?.strokePath()
}
}
class BoneView: UIView{
//--------------------------------------------------------------------------
var startPoint: CGPoint!
var endPoint: CGPoint!
//--------------------------------------------------------------------------
init(frame: CGRect, start: CGPoint, end: CGPoint)
{
self.startPoint = start
self.endPoint = end
super.init(frame: frame)
}
//--------------------------------------------------------------------------
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//--------------------------------------------------------------------------
/// This is where any drawing _should_ go
/// - Parameter rect: this is the bounding rectangle of the view
override func draw(_ rect: CGRect) {
self.addSubview(LineView(frame: rect,
start: self.startPoint,
end: self.endPoint))
}
//--------------------------------------------------------------------------
/// update the points of the line and set the view to redraw
/// - Parameters:
/// - start: new start point
/// - end: new end point
func update(start: CGPoint, end: CGPoint)
{
for subview in self.subviews{
subview.removeFromSuperview()
}
self.startPoint = start
self.endPoint = end
setNeedsDisplay();
}
//--------------------------------------------------------------------------
}
That looks great thanks Matt.
I will look into improving the way things get drawn if I have any time left before the submission, if not I will likely want to improve it after anyway. For now I will focus on the logic for the actual form check for my submission.
do #2 first