Draw Scenekit Object Between Two Points

Draw SceneKit object between two points

Both solutions mentioned above work very well and I can contribute third solution to this question.

//extension code starts

func normalizeVector(_ iv: SCNVector3) -> SCNVector3 {
let length = sqrt(iv.x * iv.x + iv.y * iv.y + iv.z * iv.z)
if length == 0 {
return SCNVector3(0.0, 0.0, 0.0)
}

return SCNVector3( iv.x / length, iv.y / length, iv.z / length)

}

extension SCNNode {

func buildLineInTwoPointsWithRotation(from startPoint: SCNVector3,
to endPoint: SCNVector3,
radius: CGFloat,
color: UIColor) -> SCNNode {
let w = SCNVector3(x: endPoint.x-startPoint.x,
y: endPoint.y-startPoint.y,
z: endPoint.z-startPoint.z)
let l = CGFloat(sqrt(w.x * w.x + w.y * w.y + w.z * w.z))

if l == 0.0 {
// two points together.
let sphere = SCNSphere(radius: radius)
sphere.firstMaterial?.diffuse.contents = color
self.geometry = sphere
self.position = startPoint
return self

}

let cyl = SCNCylinder(radius: radius, height: l)
cyl.firstMaterial?.diffuse.contents = color

self.geometry = cyl

//original vector of cylinder above 0,0,0
let ov = SCNVector3(0, l/2.0,0)
//target vector, in new coordination
let nv = SCNVector3((endPoint.x - startPoint.x)/2.0, (endPoint.y - startPoint.y)/2.0,
(endPoint.z-startPoint.z)/2.0)

// axis between two vector
let av = SCNVector3( (ov.x + nv.x)/2.0, (ov.y+nv.y)/2.0, (ov.z+nv.z)/2.0)

//normalized axis vector
let av_normalized = normalizeVector(av)
let q0 = Float(0.0) //cos(angel/2), angle is always 180 or M_PI
let q1 = Float(av_normalized.x) // x' * sin(angle/2)
let q2 = Float(av_normalized.y) // y' * sin(angle/2)
let q3 = Float(av_normalized.z) // z' * sin(angle/2)

let r_m11 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3
let r_m12 = 2 * q1 * q2 + 2 * q0 * q3
let r_m13 = 2 * q1 * q3 - 2 * q0 * q2
let r_m21 = 2 * q1 * q2 - 2 * q0 * q3
let r_m22 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3
let r_m23 = 2 * q2 * q3 + 2 * q0 * q1
let r_m31 = 2 * q1 * q3 + 2 * q0 * q2
let r_m32 = 2 * q2 * q3 - 2 * q0 * q1
let r_m33 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3

self.transform.m11 = r_m11
self.transform.m12 = r_m12
self.transform.m13 = r_m13
self.transform.m14 = 0.0

self.transform.m21 = r_m21
self.transform.m22 = r_m22
self.transform.m23 = r_m23
self.transform.m24 = 0.0

self.transform.m31 = r_m31
self.transform.m32 = r_m32
self.transform.m33 = r_m33
self.transform.m34 = 0.0

self.transform.m41 = (startPoint.x + endPoint.x) / 2.0
self.transform.m42 = (startPoint.y + endPoint.y) / 2.0
self.transform.m43 = (startPoint.z + endPoint.z) / 2.0
self.transform.m44 = 1.0
return self
}
}

//extension ended.

//in your code, you can like this.
let twoPointsNode1 = SCNNode()
scene.rootNode.addChildNode(twoPointsNode1.buildLineInTwoPointsWithRotation(
from: SCNVector3(1,-1,3), to: SCNVector3( 7,11,7), radius: 0.2, color: .cyan))
//end

you can reference http://danceswithcode.net/engineeringnotes/quaternions/quaternions.html

BTW, you will get same result when you use a cylinder to make a line between two points from above 3 methods. But indeed, they will have different normal lines. In another words, if you use box between two points, sides of box, except top and bottom, will face different direction from above 3 methods.

let me know pls if you need further explanation.

SceneKit Object between two points

I've good news for you ! You can link two points and put a SCNNode on this Vector !

Take this and enjoy drawing line between two points !

class   CylinderLine: SCNNode
{
init( parent: SCNNode,//Needed to line to your scene
v1: SCNVector3,//Source
v2: SCNVector3,//Destination
radius: CGFloat,// Radius of the cylinder
radSegmentCount: Int, // Number of faces of the cylinder
color: UIColor )// Color of the cylinder
{
super.init()

//Calcul the height of our line
let height = v1.distance(v2)

//set position to v1 coordonate
position = v1

//Create the second node to draw direction vector
let nodeV2 = SCNNode()

//define his position
nodeV2.position = v2
//add it to parent
parent.addChildNode(nodeV2)

//Align Z axis
let zAlign = SCNNode()
zAlign.eulerAngles.x = CGFloat(M_PI_2)

//create our cylinder
let cyl = SCNCylinder(radius: radius, height: CGFloat(height))
cyl.radialSegmentCount = radSegmentCount
cyl.firstMaterial?.diffuse.contents = color

//Create node with cylinder
let nodeCyl = SCNNode(geometry: cyl )
nodeCyl.position.y = CGFloat(-height/2)
zAlign.addChildNode(nodeCyl)

//Add it to child
addChildNode(zAlign)

//set constraint direction to our vector
constraints = [SCNLookAtConstraint(target: nodeV2)]
}

override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

private extension SCNVector3{
func distance(receiver:SCNVector3) -> Float{
let xd = receiver.x - self.x
let yd = receiver.y - self.y
let zd = receiver.z - self.z
let distance = Float(sqrt(xd * xd + yd * yd + zd * zd))

if (distance < 0){
return (distance * -1)
} else {
return (distance)
}
}
}

SceneKit – Drawing a line between two points

There are lots of ways to do this.

As noted, your custom geometry approach has some disadvantages. You should be able to correct the problem of it being invisible from one side by giving its material the doubleSided property. You still may have issues with it being two-dimensional, though.

You could also modify your custom geometry to include more triangles, so you get a tube shape with three or more sides instead of a flat rectangle. Or just have two points in your geometry source, and use the SCNGeometryPrimitiveTypeLine geometry element type to have Scene Kit draw a line segment between them. (Though you won't get as much flexibility in rendering styles with line drawing as with shaded polygons.)

You can also use the SCNCylinder approach you mentioned (or any of the other built-in primitive shapes). Remember that geometries are defined in their own local (aka Model) coordinate space, which Scene Kit interprets relative to the coordinate space defined by a node. In other words, you can define a cylinder (or box or capsule or plane or whatever) that's 1.0 units wide in all dimensions, then use the rotation/scale/position or transform of the SCNNode containing that geometry to make it long, thin, and stretching between the two points you want. (Also note that since your line is going to be pretty thin, you can reduce the segmentCounts of whichever built-in geometry you're using, because that much detail won't be visible.)

Yet another option is the SCNShape class that lets you create an extruded 3D object from a 2D Bézier path. Working out the right transform to get a plane connecting two arbitrary points sounds like some fun math, but once you do it you could easily connect your points with any shape of line you choose.

Cylinder Orientation between two points on a sphere, Scenekit, Quaternions IOS

Here's an entire method using Objective-C

First, here's how you use it:

SCNNode * testNode = [self lat1:-35 lon1:108 height1:tall lat2:-35 lon2:30 height2:0];

Inputs:

1rst location
lat1 = latitude of 1rst location
lon1 = longitude of 1rst location
height1 = distance from earth for 1rst location
lat2 = latitude of 2nd location
lon2 = latitude of 2nd location
height2 = distance from earth for 2nd location

The second method creates the SCNVector3 points for each location in question above:

-(SCNNode *)lat1:(double)lat1 lon1:(double)lon1 height1:(float)height1 lat2:(double)lat2 lon2:(double)lon2 height2:(float)height2 {
SCNVector3 positions[] = {[self lat:lat1 lon:lon1 height:height1], [self lat:lat2 lon:lon2 height:height2]};

float cylHeight = GLKVector3Distance(SCNVector3ToGLKVector3(positions[0]), SCNVector3ToGLKVector3(positions[1]))/4;

SCNCylinder * masterCylinderNode = [SCNCylinder cylinderWithRadius:0.05 height:cylHeight];

SCNMaterial *material = [SCNMaterial material];
[[material diffuse] setContents:[SKColor whiteColor]];
material.lightingModelName = SCNLightingModelConstant;
material.emission.contents = [SKColor whiteColor];
[masterCylinderNode setMaterials:@[material]];

SCNNode *mainLocationPointNodeTestA = [mainLocationPointNode clone];
SCNNode *mainLocationPointNodeTestB = [mainLocationPointNode clone];

mainLocationPointNodeTestA.position = positions[0];
mainLocationPointNodeTestB.position = positions[1];

SCNNode * mainParentNode = [SCNNode node];
SCNNode * tempNode2 =[SCNNode nodeWithGeometry:masterCylinderNode];

[mainParentNode addChildNode:mainLocationPointNodeTestA];
[mainParentNode addChildNode:mainLocationPointNodeTestB];
[mainParentNode addChildNode:tempNode2];

[mainParentNode setName:@"parentToLineNode"];

tempNode2.position = SCNVector3Make((positions[0].x+positions[1].x)/2, (positions[0].y+positions[1].y)/2, (positions[0].z+positions[1].z)/2);
tempNode2.pivot = SCNMatrix4MakeTranslation(0, cylHeight*1.5, 0);

GLKVector3 normalizedVectorStartingPosition = GLKVector3Make(0.0, 1.0, 0.0);
GLKVector3 magicAxis = GLKVector3Normalize(GLKVector3Subtract(GLKVector3Make(positions[0].x/2, positions[0].y/2, positions[0].z/2), GLKVector3Make(positions[1].x/2, positions[1].y/2, positions[1].z/2)));

GLKVector3 rotationAxis = GLKVector3CrossProduct(normalizedVectorStartingPosition, magicAxis);
CGFloat rotationAngle = GLKVector3DotProduct(normalizedVectorStartingPosition, magicAxis);

GLKVector4 rotation = GLKVector4MakeWithVector3(rotationAxis, acos(rotationAngle));
tempNode2.rotation = SCNVector4FromGLKVector4(rotation);

return mainParentNode;
}

This second method uses hard coded numbers for earth's radius and curvature, I'm showing this just to show the numbers required for total 100% accuracy, this is how it works. You'll want to change this to the correct dimensions for your scene, obviously, but here's the method. This is an adaptation of methods used by Link. An explanation an be found here: Link. I put this together very quickly but it works and is accurate, feel free to change the number formats to your liking.

-(SCNVector3)lat:(double)lat lon:(double)lon height:(float)height {
double latd = 0.0174532925;
double latitude = latd*lat;
double longitude = latd*lon;

Float64 rad = (Float64)(6378137.0);
Float64 f = (Float64)(1.0/298.257223563);

double cosLat = cos(latitude);

double sinLat = sin(latitude);

double FF = pow((1.0-f), 2);
double C = 1/(sqrt(pow(cosLat,2) + FF * pow(sinLat,2)));
double S = C * FF;

double x = ((rad * C)*cosLat * cos(longitude))/(1000000/(1+height));
double y = ((rad * C)*cosLat * sin(longitude))/(1000000/(1+height));
double z = ((rad * S)*sinLat)/(1000000/(1+height));

return SCNVector3Make(y+globeNode.position.x, z+globeNode.position.y, x+globeNode.position.z);
}


Related Topics



Leave a reply



Submit