I want to put an indicator in my app showing which direction an object is facing. I'd like to obtain the rotation in world space to do this but don't know how to extract it from worldTransform. Can someone please tell me how to do this, or is there a better way? I just need rotation in radians on the Y axis from the original position. Thanks.
SCNNode's have a bunch of options for orientations and worldTransform is part of that structure. You can also get the nodes orientation/eulerAngles, etc.
https://developer.apple.com/documentation/scenekit/scnnode/1408048-orientation
GLKit Functions have conversions for degrees to radians, like:
SCNAction.rotateBy(x: CGFloat(Float(GLKMathDegreesToRadians(-90))), y: 0, z: 0, duration: 0)
World Transform: CATransForm3D:
init(m11: CGFloat, m12: CGFloat, m13: CGFloat, m14: CGFloat, m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat)
These are the rotations
Edit - is it that you need the definitions for transform/worldTransform, matrices... like m11, etc?
var nRight: SCNVector3 = SCNVector3Make(0, 0, 0)
var nUp: SCNVector3 = SCNVector3Make(0, 0, 0)
var nScale: SCNVector3 = SCNVector3Make(1, 1, 1)
var nTarget: SCNVector3 = SCNVector3Make(0, 0, 0)
vNode.transform.m11 = nRight.x
vNode.transform.m12 = nRight.y
vNode.transform.m13 = nRight.z
vNode.transform.m21 = nUp.x
vNode.transform.m22 = nUp.y
vNode.transform.m23 = nUp.z
vNode.transform.m31 = nTarget.x
vNode.transform.m32 = nTarget.y
vNode.transform.m33 = nTarget.z
Best guess = GLKMathDegreesToRadians(node.eulerAngule.y) is what you are really looking for.
If it has to come from worldTransform, I "think" it's this, but I'm no math expert:
nUp.x = vNode.worldTransform.m21;
nUp.y = vNode.worldTransform.m22;
nUp.z = vNode.worldTransform.m23;
GLKMathDegreesToRadians(nUp);
Related
I am trying to create a FluidView shape in SwiftUI which acts like a fluid in a container, such that when the device is at a particular angle, so too is the shape / 'fluid'. The shape also has a specific capacity, percentFilled, which indicates how much of the parent's view should be filled.
Using these constraints, the invariant for the class is
lines.area == rect.area * percentFilled
where lines is the quadrilateral and rect is the bounding rectangle. This invariant implies that the 'volume' of the shape remains constant for each percentFilled irrespective of the tilt angle.
Here is what I have so far:
/// A View made using a specified angle and amount to fill
/// - Invariant: The area of the view is exactly equal to the area of the rectangle of the parent view times `percentFilled`
struct FluidView: Shape {
var angle: CGFloat = 0.0
var percentFilled: CGFloat = 0
/// Creates a new FluidView
/// - Parameters:
/// - angle: A value in the range `0...1`. A value of `0` indicates the view is horizontal, and an angle of `1` indicates the view is vertical (horizontal if viewed as landscape)
/// - percentFilled: the amount of the view bounds to fill represented as a value in the range `0...1`. A value of `x` indicates that `x * 100`% of the parent view is covered by this view
init(angle: CGFloat = 0, percentFilled: CGFloat = 0) {
precondition(0...1 ~= angle)
precondition(0...1 ~= percentFilled)
self.angle = angle
self.percentFilled = percentFilled
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: rect.height * (1 - percentFilled))) // top left
let lines = [
(0, rect.height ), // bottom left
(rect.width * 1 / (1 + angle - percentFilled), rect.height ), // bottom right
(rect.width * 1 / (1 + angle - percentFilled), rect.height * (1 + angle - percentFilled)), // top right
(0, rect.height * (1 - angle - percentFilled)) // top left
].map { x, y in
// make sure no points exceed the bounds
CGPoint(x: min(rect.width, x), y: min(rect.height, y))
}
// invariant
assert(lines.area == rect.area * percentFilled)
path.addLines(lines)
return path
}
}
I feel like what I have currently is somewhat close to the goal, however the invariant fails. I believe that my y-coordinates are correct, however I think my calculations for the x-coordinates have to change, but I'm not sure to what they should change.
Any help would be really appreciated, thanks!
Try something like this,
struct FilledShape<S: Shape>: View {
let shape: S
#State var angle: Angle = .zero
#State var percentFull: CGFloat
var gradient: Gradient {
Gradient(stops: [Gradient.Stop(color: .red, location: 0),
Gradient.Stop(color: .red, location: percentFull),
Gradient.Stop(color: .blue, location: percentFull),
Gradient.Stop(color: .blue, location: 1)])
}
var body: some View {
shape.rotation(angle)
.fill(LinearGradient(gradient: gradient, startPoint: .bottom, endPoint: .top))
}
}
struct ContentView: View {
#State var angle: Angle = .degrees(30)
var body: some View {
FilledShape(shape: Rectangle(), angle: angle, percentFull: 0.3).frame(width: 100, height: 100)
}
}
Thing is, percent full is really the percent up along the y axis, not the percent of the area filled. You could use some kind of numeric method with GeometryReader to get the area and read the y-value at the appropriate filled area sum (or if you just use quadrilaterals it's easier). By brute force:
extension Shape {
func area(in box: CGRect) -> Int {
var area = 0
for x in 0..<Int(box.width) {
for y in 0..<Int(box.height) {
let point = CGPoint(x: x, y: y)
if self.contains(point) {
area += 1
}
}
}
return area
}
}
As a different approach, look into SpriteKit and SKPhysicsBody.
I am trying to position a box in an AR view and I need it to be at the same position as my AR Plane, or where the Plane Anchor is detected. My code is trying to call to the addBoxEToPlane func so that I can position BoxE the same as the plane, but it gives me the error "Value of tuple type '()' has no member 'position' ". If anybody can help me call to that function, or basically position that box the same as the AR Plane, I would really appreciate it!
func addBoxEToPlane(){
let boxE = SCNNode(geometry: SCNBox(width: 2.0, height: 2.0, length: 2.0, chamferRadius: 0))
boxE.geometry?.firstMaterial?.diffuse.contents = UIColor.red
}
func updateWith(anchor: ARPlaneAnchor) {
plane.width = CGFloat(anchor.extent.x)
plane.height = CGFloat(anchor.extent.z)
position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
if let grid = plane.materials.first as? GridMaterial {
grid.updateWith(anchor: anchor)
}
let XCoord = (plane.width)/2
let YCoord = (plane.height)/2
addBoxEToPlane().position = SCNVector3(XCoord, YCoord, anchor.center.z)
}
I want to create an svg like that:
So, a circumference divided into two parts by a chord. The two sections must have different colors.
I want to draw this object in SVG. I think I need to use the PATH tag, right? Or is there another way to do it?
What points do I need to draw the object? I'm a bit confused..
Yes. It is a <path> element that you will need.
Paths always start with an M (move) command. You'll also need an A (arc) command, and probably an L line command for the line that bisects the circle.
For the arc command, you just need to know the X and Y coordinates of the start and end points (B and C). Plus the radius of the circle. It is important to have accurate coordinates for the start and end points of an arc command. Small discrepancies can cause the position of the circle to move around quite a bit.
In the following demo, I have chosen to calculate the B and C positions based on their angle from the centre. Plus setting the path description attribute from code, allows me to document for you what each of the parameters are for.
// Radius of the circle
var radius = 80;
// Centre coordinate of the circle
var Ox = 100;
var Oy = 100;
// Angles of each point from which we calculate their X and Y coordinates.
// Here, 0 degrees is East, and angle increases in a clockwise direction.
var angleB = 285; // degrees
var angleC = 35;
var B = angleToCoords(angleB, Ox, Oy, radius);
var C = angleToCoords(angleC, Ox, Oy, radius);
// Get the "major segment" path element
var majorPath = document.getElementById("major");
// Set the path description for the "major segment"
majorPath.setAttribute("d", ['M', B.x, B.y, // Move to point B
'L', C.x, C.y, // Line to point C
'A', radius, radius, // X radius and Y radius of the arc
0, // ellipse angle
1, // large arc flag (1 indicates we want the larger of the two possible arcs between the points
1, // clockwise direction flag
B.x, B.y, // arc end point is back at point B
'Z'].join(" ")); // Z command closes the path
// Get the "minor segment" path element
var minorPath = document.getElementById("minor");
// Set the path description for the "minor segment"
minorPath.setAttribute("d", ['M', B.x, B.y, // Move to point B
'A', radius, radius, // X radius and Y radius of the arc
0, // ellipse angle
0, // large arc flag (0 indicates we want the smaller of the two possible arcs between the points
1, // clockwise direction flag
C.x, C.y, // arc end point is at point C
'L', B.x, B.y, // Line to point B
'Z'].join(" ")); // Z command closes the path
// Function to convert from an angle to an X and Y position
function angleToCoords(angleInDegrees, centreX, centreY, radius)
{
var angleInRadians = angleInDegrees * Math.PI / 180;
return {
'x': centreX + (radius * Math.cos(angleInRadians)),
'y': centreY + (radius * Math.sin(angleInRadians))
}
}
path {
stroke: black;
stroke-width: 1;
}
#major {
fill: #78dcdc;
}
#minor {
fill: #aaffaa;
}
<svg width="200" height="200">
<path id="major" d="" />
<path id="minor" d="" />
</svg>
Why is my SKPhysicsBody positioned way off from the SKSpriteNode that it is attached to? This is how I create the physics body:
self.pin = [SKSpriteNode spriteNodeWithImageNamed:[IPGameManager sharedGameData].world.player.ship.type];
self.pin.position = CGPointMake([IPGameManager sharedGameData].world.player.location.xCoordinate, [IPGameManager sharedGameData].world.player.location.yCoordinate);
self.pin.userInteractionEnabled = NO;
NSLog(#"Pin width: %f", self.pin.size.width);
NSLog(#"Pin position: %f %f", self.pin.position.x, self.pin.position.y);
self.pin.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.pin.size.width * 0.5 center:self.pin.position];
self.pin.physicsBody.dynamic = YES;
self.pin.physicsBody.allowsRotation = NO;
self.pin.physicsBody.friction = 0.0;
self.pin.physicsBody.linearDamping = 0.0;
By setting skView.showsPhysics = YES;, it shows the physics bodies and the circle is WAY off the SKSpriteNode. Any idea why?
You are setting the center position of the physicsBody to the be the CGPoint value of self.pin.position, which is a point in self.pin's parent node's coordinate space. If you are looking to have the physics body simply be centered in self.pin, then you would either use a 0,0 position for center (or not use the method with center at all), or an offset point if you have moved the anchor position of self.pin:
[SKPhysicsBody bodyWithCircleOfRadius:self.pin.size.width * 0.5 center:CGPointZero];
//OR
[SKPhysicsBody bodyWithCircleOfRadius:self.pin.size.width * 0.5 ]
A Google Map marker can take a complex svg path as its icon as in something like:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
};
var baseIcon = {
path: "M0,0 " + baseSvg["x1"] + baseSvg["x2"],
fillColor: "#000000",
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
};
which is then fed into a marker:
var marker = new google.maps.Marker({
position: new google.maps.LatLng(somelat, somelng),
icon: baseIcon
});
All good. But it only draws in a single colour (black in this example). So, can they only have a single colour or is there a way to have multi-coloured symbols? Using the example code, x1 would be red and x2 would be green.
Note that this construct is borrowed from here: How do I indicate transparency in an SVG path in Google Maps API v3? and it works nicely.
I just did the same thing and I think you have to draw two markers with essentially identical data, but with the path and in this case fillColor properties changed:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
},
baseIcon = {
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
},
markerPos = new google.maps.LatLng(somelat, somelng),
// psuedo code for cloning an object since that
// is out of scope for this question
greenIcon = Object.clone(baseIcon),
redIcon = Object.clone(baseIcon);
greenIcon.path = baseSvg.x1;
greenIcon.fillColor = "#0f0";
redIcon.path = baseSvg.x2;
redIcon.fillColor = "#f00";
var marker1 = new google.maps.Marker({
position: markerPos,
map: map,
icon: greenIcon
});
var marker2 = new google.maps.Marker({
position: markerPos,
map: map,
icon: redIcon
});
obviously not optimized js, but you get the idea.
When it comes to opacity, strokeOpacity is your guy. Check the maps.Symbol class for more information on symbol properties.