Deriving the transformation matrix is a fairly common requirement for shaders. Are there and wgsl standard libraries for doing this sort of thing? i.e. even mat4x4 - mat4x4 multiplication would be useful!
I've written a rough draft below, but it seems like a fairly longwinded way of doing it
// Create a homogeneous transformation matrix from a translation vector.
fn mk_translation_matrix(v: vec3<f32>) -> mat4x4<f32>{
let c_1: vec4<f32> = vec4<f32>(1., 0., 0., v.x);
let c_2: vec4<f32> = vec4<f32>(0., 1., 0., v.y);
let c_3: vec4<f32> = vec4<f32>(0., 0., 1., v.z);
let c_4: vec4<f32> = vec4<f32>(0., 0., 0., 1.);
let translation_matrix = mat4x4<f32>(c_1, c_2, c_3, c_4);
return translation_matrix;
}
fn mk_rotation_matrix(q: vec4<f32>) -> mat4x4<f32> {
let m11 = 2. * (q.x * q.x + q.y * q.y) - 1.;
let m12 = 2. * (q.y * q.z - q.x * q.w);
let m13 = 2. * (q.y * q.w - q.x * q.z);
let m21 = 2. * (q.y * q.z + q.x * q.w);
let m22 = 2. * (q.x * q.x + q.z * q.z) - 1.;
let m23 = 2. * (q.z * q.w + q.x * q.y);
let m31 = 2. * (q.y * q.w - q.x * q.z);
let m32 = 2. * (q.z * q.w + q.x * q.y);
let m33 = 2. * (q.x * q.x + q.w * q.w) - 1.;
let c_1: vec4<f32> = vec4<f32>(m11, m21, m31, 0.);
let c_2: vec4<f32> = vec4<f32>(m12, m22, m32, 0.);
let c_3: vec4<f32> = vec4<f32>(m13, m23, m33, 0.);
let c_4: vec4<f32> = vec4<f32>(0., 0., 0., 1.);
let rotation_matrix: mat4x4<f32> = mat4x4<f32>(c_1, c_2, c_3, c_4);
return rotation_matrix;
}
fn mat4_mul(A: mat4x4<f32>, B: mat4x4<f32> ) -> mat4x4<f32> {
// rows of A
let r_1: vec4<f32> = transpose(A)[0];
let r_2: vec4<f32> = transpose(A)[1];
let r_3: vec4<f32> = transpose(A)[2];
let r_4: vec4<f32> = transpose(A)[3];
//cols of B
let c_1: vec4<f32> = B[0];
let c_2: vec4<f32> = B[1];
let c_3: vec4<f32> = B[2];
let c_4: vec4<f32> = B[3];
let multiplied = mat4x4<f32>(
vec4<f32>(dot(r_1 , c_1), dot(r_2, c_1), dot(r_3, c_1), dot(c_4,c_1)),
vec4<f32>(dot(r_1, c_2), dot(r_2, c_2), dot(r_3, c_2), dot(c_4, c_2)),
vec4<f32>(dot(r_1, c_3), dot(r_2, c_3), dot(r_3, c_3), dot(c_4, c_3)),
vec4<f32>(dot(r_1, c_4), dot(r_2, c_4), dot(r_3, c_4), dot(c_4, c_4)),
);
return multiplied;
}
fn mk_transformation_matrix(position: vec3<f32>, rotation: vec4<f32>) -> mat4x4<f32> {
let transformation_matrix = mat4_mul(mk_translation_matrix(position), mk_rotation_matrix(rotation));
return transformation_matrix;
}
From the WGSL spec.
Multiplication (Section "Matrix arithmetic")
Transpose
Related
I'm working on implementing normal mapping (using Swift/Metal). However, I think I've made a mistake when calculating the tangent and bitangent, and I can't find the issue.
Here is a screenshot with normal mapping:
basic shading with normal map & basic shading, normal mapping normals
And here is a screenshot only using the normals provided by the obj file:
basic shading without normal map, normals.
Here is the code for calculating the tangent and bitangent:
func calculateTangentAndBitangent(v1: VertexData, v2: VertexData, v3: VertexData) -> (vec3, vec3) {
let pos1 = v1.position
let pos2 = v2.position
let pos3 = v3.position
let uv1 = v1.uv
let uv2 = v2.uv
let uv3 = v3.uv
let e1 = pos2 - pos1
let e2 = pos3 - pos1
let x1 = uv2.x - uv1.x
let x2 = uv3.x - uv1.x
let y1 = uv2.y - uv1.y
let y2 = uv3.y - uv1.y
let r = 1.0 / (x1 * y2 - x2 * y1)
let tangent = (e1 * y2 - e2 * y1) * r
let bitangent = (e2 * x1 - e1 * x2) * r
return (tangent, bitangent)
}
for i in stride(from: 0, to: vertexBuffer.count, by: 3) {
let v1 = vertexBuffer[i]
let v2 = vertexBuffer[i+1]
let v3 = vertexBuffer[i+2]
let tb = calculateTangentAndBitangent(v1: v1, v2: v2, v3: v3)
tangents[normalIndices[i]] += tb.0
tangents[normalIndices[i+1]] += tb.0
tangents[normalIndices[i+2]] += tb.0
bitangents[normalIndices[i]] += tb.1
bitangents[normalIndices[i+1]] += tb.1
bitangents[normalIndices[i+2]] += tb.1
}
for i in 0..<vertexBuffer.count {
var v = vertexBuffer[i]
v.tangent = normalize(tangents[normalIndices[i]])
v.bitangent = normalize(bitangents[normalIndices[i]])
vertices.append(v.vertex)
}
Vertex shader:
out.tangent = normalize((modelUniforms.modelMatrix * float4(in.tangent, 0)).xyz);
out.bitangent = normalize((modelUniforms.modelMatrix * float4(in.bitangent, 0)).xyz);
out.normal = normalize((modelUniforms.modelMatrix * float4(in.normal, 0)).xyz);
Fragment shader:
constexpr sampler linearSampler(mip_filter::linear,
mag_filter::linear,
min_filter::linear);
float3 normalSample = float3(normalMap.sample(linearSampler, in.texCoord).xyz);
normalSample = normalize(normalSample * 2 - 1);
float3 normal = normalize(in.tangent * normalSample.x +
in.bitangent * normalSample.y +
in.normal * normalSample.z);
float3 L = normalize(sceneUniforms.sun.direction);
float3 NdL = max(dot(in.normal, L), 0.0);
half3 diff = half3(NdL);
I've spent a lot of time looking into this and I still couldn't figure out what the issue is.
If you use MTKTextureLoader to load your textures make sure you specify MTKTextureLoader.Option.SRGB: false when loading normal maps so that the image data is treated as linear pixel data, else the normals will be all wrong.
https://developer.apple.com/documentation/metalkit/mtktextureloader/option/1536032-srgb
Im trying to convert my quats to euler, but out of x/y/z components, only my X has accurate value and y/z is incorrect :- ( can any1 have a look/help ?
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Double>{
var angles = SIMD3<Double>();
let qfloat = quat.vector
let q = SIMD4<Double>(Double(qfloat.x),Double(qfloat.y),Double(qfloat.z), Double(qfloat.w))
// roll (x-axis rotation)
let sinr_cosp : Double = 2.0 * (q.w * q.x + q.y * q.z);
let cosr_cosp : Double = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
angles.x = atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
let sinp : Double = 2 * (q.w * q.y - q.z * q.x);
if (abs(sinp) >= 1){
angles.y = copysign(Double.pi / 2, sinp); // use 90 degrees if out of range
}
else{
angles.y = asin(sinp);
}
// yaw (z-axis rotation)
let siny_cosp : Double = 2 * (q.w * q.z + q.x * q.y);
let cosy_cosp : Double = 1 - 2 * (q.y * q.y + q.z * q.z);
angles.z = atan2(siny_cosp, cosy_cosp);
return angles;
}
Wiki example converted to swifht.
TIA
My solution would be to let the (SceneKit) library do it:
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Float>{
let n = SCNNode()
n.simdOrientation = quat
return n.simdEulerAngles
}
I took a look at and converted it to Swift,
https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
It works for me.
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Float>{
var angles = SIMD3<Float>();
let qfloat = quat.vector
// heading = x, attitude = y, bank = z
let test = qfloat.x*qfloat.y + qfloat.z*qfloat.w;
if (test > 0.499) { // singularity at north pole
angles.x = 2 * atan2(qfloat.x,qfloat.w)
angles.y = (.pi / 2)
angles.z = 0
return angles
}
if (test < -0.499) { // singularity at south pole
angles.x = -2 * atan2(qfloat.x,qfloat.w)
angles.y = -(.pi / 2)
angles.z = 0
return angles
}
let sqx = qfloat.x*qfloat.x;
let sqy = qfloat.y*qfloat.y;
let sqz = qfloat.z*qfloat.z;
angles.x = atan2(2*qfloat.y*qfloat.w-2*qfloat.x*qfloat.z , 1 - 2*sqy - 2*sqz)
angles.y = asin(2*test)
angles.z = atan2(2*qfloat.x*qfloat.w-2*qfloat.y*qfloat.z , 1 - 2*sqx - 2*sqz)
return angles
}
I just created a rectangle using four vertices in metal. I just need to rotate it. So I use a model metrics.Here is my vertex shader.
vertex VertexOutTexture vertex_shader_texture(const VertexInTexture vertices [[stage_in]],
constant ModelConstant &modelConstants[[buffer(1)]],
VertexOutTexture v;
v.position = modelConstants.modelMatrix*float4(vertices.position,1);
v.color = vertices.color;
v.textureCoordinates = vertices.textureCoordinates;
return v;
}
it rotate. But shape is changed. So I used projection transformation which converts the node’s coordinates from camera coordinates to normalized coordinates.
I create projrction matrix:
var sceneConstants = ScenceConstants()
set its value:
sceneConstants.projectionMatrix = matrix_float4x4(prespectiveDegreesFov:45, aspectRatio:Float(1.0),nearZ:0.1,farZ:100)
where init mathod is in math.h
init(prespectiveDegreesFov degreesFov:Float, aspectRatio:Float,nearZ:Float,farZ:Float){
let fov = radians(fromDegrees: degreesFov)
let y = 1/tan(fov*0.5)
let x = y/aspectRatio
let z1 = farZ/(nearZ - farZ)
let w = (z1*nearZ)
columns = (
float4(x, 0, 0, 0),
float4(0, y, 0, 0),
float4(0, 0, z1,-1),
float4(0, 0, w, 0)
)
}
send commands to GPU:
commandEncoder.setVertexBytes(&sceneConstants, length: MemoryLayout<ScenceConstants>.stride, index: 2)
change my vertex shader:
v.position = sceneConstants.projectionMatrix* modelConstants.modelMatrix*float4(vertices.position ,1 );
But it did not work.
before rotation:
after rotation:
I have atached math functions I am using below.
func radians(fromDegrees degrees:Float) ->Float{
return (degrees/100)*Float(Double.pi)
}
extension matrix_float4x4 {
init(prespectiveDegreesFov degreesFov:Float, aspectRatio:Float,nearZ:Float,farZ:Float){
let fov = radians(fromDegrees: degreesFov)
let y = 1/tan(fov*0.5)
let x = y/aspectRatio
let z1 = farZ/(nearZ - farZ)
let w = (z1*nearZ)
columns = (
float4(x, 0, 0, 0),
float4(0, y, 0, 0),
float4(0, 0, z1,-1),
float4(0, 0, w, 0)
)
}
mutating func scale(axis: float3){
var result = matrix_identity_float4x4
let x :Float = axis.x
let y :Float = axis.y
let z :Float = axis.z
result.columns = (
float4(x,0,0,0),
float4(0,y,0,0),
float4(0,0,z,0),
float4(0,0,0,1)
)
print("self:\(self)")
self = matrix_multiply(self, result)
}
mutating func translate(direction: float3){
var result = matrix_identity_float4x4
let x :Float = direction.x
let y :Float = direction.y
let z :Float = direction.z
result.columns = (
float4(1,0,0,0),
float4(0,1,0,0),
float4(0,0,1,0),
float4(x,y,z,1)
)
print("self:\(self)")
self = matrix_multiply(self, result)
}
mutating func rotate(angle: Float ,axis: float3){
var result = matrix_identity_float4x4
let x :Float = axis.x
let y :Float = axis.y
let z :Float = axis.z
let c: Float = cos(angle)
let s:Float = sin(angle)
let mc :Float = (1 - c)
let r1c1: Float = x * x * mc + c
let r2c1: Float = x * y * mc + z * s
let r3c1: Float = x * z * mc - y * s
let r4c1: Float = 0.0
let r1c2: Float = y * x * mc - z * s
let r2c2: Float = y * y * mc + c
let r3c2: Float = y * z * mc + x * s
let r4c2: Float = 0.0
let r1c3: Float = z * x * mc + y * s
let r2c3: Float = z * y * mc - x * s
let r3c3: Float = z * z * mc + c
let r4c3: Float = 0.0
let r1c4: Float = 0.0
let r2c4: Float = 0.0
let r3c4: Float = 0.0
let r4c4: Float = 1.0
result.columns = (
float4(r1c1,r2c1,r3c1,r4c1),
float4(r1c2,r2c2,r3c2,r4c2),
float4(r1c3,r2c3,r3c3,r4c3),
float4(r1c4,r2c4,r3c4,r4c4)
)
print("Result:\(result)")
self = matrix_multiply(self, result)
}
}
How can I fix this issue?Any suggestions please?
Having made some progress in the geometry side of things I'm moving on to putting together an entire scene. That scene has a couple dozen objects, each defined by a bounding cube whose corners are specified by two SCNVector3s (originally two sets of x,y,z).
Here's an example of what I have so far - it's an 11-element log-periodic antenna, like the old school TV antennas from the 70s. Each of the grey lines is an "element", typically made of aluminum rod. I used SCNCylinders from +ve to -ve Y and the entire thing is less than 100 lines (SK is pretty amazing).
The problem is what happens if the elements are not symmetrical across X and thus the SCNCylinder has to be rotated. I found this example, but I can't understand the specifics... it appears to take advantage of the fact that a sphere is symmetric so angles kind of "go away".
Does anyone have a general function that will take two 3D points and return the SCNVector3 suitable for setting the node's eulerAngle, or a similar solution?
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.
EDIT: For under or equal to IOS 11
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 point !
class CylinderLine: SCNNode
{
init( parent: SCNNode,//Needed to add destination point of your line
v1: SCNVector3,//source
v2: SCNVector3,//destination
radius: CGFloat,//somes option for the cylinder
radSegmentCount: Int, //other option
color: UIColor )// color of your node object
{
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 = Float(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 = -height/2
zAlign.addChildNode(nodeCyl)
//Add it to child
addChildNode(zAlign)
//set contrainte 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)
}
}
}
#maury-markowitz's answer worked for me, here is the latest (Swift4) version of it.
To anyone working with SCNVector3 in Swift I can only recommend to add the +-*/ operator overloads somewhere in your code (e.g. from here).
extension SCNNode {
static func lineNode(from: SCNVector3, to: SCNVector3, radius: CGFloat = 0.25) -> SCNNode {
let vector = to - from
let height = vector.length()
let cylinder = SCNCylinder(radius: radius, height: CGFloat(height))
cylinder.radialSegmentCount = 4
let node = SCNNode(geometry: cylinder)
node.position = (to + from) / 2
node.eulerAngles = SCNVector3.lineEulerAngles(vector: vector)
return node
}
}
extension SCNVector3 {
static func lineEulerAngles(vector: SCNVector3) -> SCNVector3 {
let height = vector.length()
let lxz = sqrtf(vector.x * vector.x + vector.z * vector.z)
let pitchB = vector.y < 0 ? Float.pi - asinf(lxz/height) : asinf(lxz/height)
let pitch = vector.z == 0 ? pitchB : sign(vector.z) * pitchB
var yaw: Float = 0
if vector.x != 0 || vector.z != 0 {
let inner = vector.x / (height * sinf(pitch))
if inner > 1 || inner < -1 {
yaw = Float.pi / 2
} else {
yaw = asinf(inner)
}
}
return SCNVector3(CGFloat(pitch), CGFloat(yaw), 0)
}
}
For the sake of another method, I achieved this through trigonometry. This made the code very minimal. Here is the end result:
In my case the nodes are always placed on a fixed plane that slices the Y-Axis.
// Create Cylinder Geometry
let line = SCNCylinder(radius: 0.002, height: node1.distance(to: node2))
// Create Material
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
material.lightingModel = .phong
line.materials = [material]
// Create Cylinder(line) Node
let newLine = SCNNode()
newLine.geometry = line
newLine.position = posBetween(first: node1, second: node2)
// This is the change in x,y and z between node1 and node2
let dirVector = SCNVector3Make(node2.x - node1.x, node2.y - node1.y, node2.z - node1.z)
// Get Y rotation in radians
let yAngle = atan(dirVector.x / dirVector.z)
// Rotate cylinder node about X axis so cylinder is laying down
currentLine.eulerAngles.x = .pi / 2
// Rotate cylinder node about Y axis so cylinder is pointing to each node
currentLine.eulerAngles.y = yAngle
This is the function to get the position between two nodes, place it within your class:
func posBetween(first: SCNVector3, second: SCNVector3) -> SCNVector3 {
return SCNVector3Make((first.x + second.x) / 2, (first.y + second.y) / 2, (first.z + second.z) / 2)
}
This is the extension to get the distance between nodes for the cylinder height, place it somewhere outside of your class:
extension SCNVector3 {
func distance(to destination: SCNVector3) -> CGFloat {
let dx = destination.x - x
let dy = destination.y - y
let dz = destination.z - z
return CGFloat(sqrt(dx*dx + dy*dy + dz*dz))
}
}
If you don't have one fixed axis like myself then you could do the extra trig to use this method.
Here's a solution using simd and quaternions for the rotation. I based the extension off of the answer by #Bersaelor.
I used this derivation (https://stackoverflow.com/a/1171995/6693924) to create the quaternion from two vectors. Hope this helps.
extension SCNNode {
static func lineNode(from: simd_float3, to: simd_float3, radius : CGFloat = 0.25) -> SCNNode
{
let vector = to - from
let height = simd_length(vector)
//cylinder
let cylinder = SCNCylinder(radius: radius, height: CGFloat(height))
cylinder.firstMaterial?.diffuse.contents = UIColor.white
//line node
let lineNode = SCNNode(geometry: cylinder)
//adjust line position
let line_axis = simd_float3(0, height/2, 0)
lineNode.simdPosition = from + line_axis
let vector_cross = simd_cross(line_axis, vector)
let qw = simd_length(line_axis) * simd_length(vector) + simd_dot(line_axis, vector)
let q = simd_quatf(ix: vector_cross.x, iy: vector_cross.y, iz: vector_cross.z, r: qw).normalized
lineNode.simdRotate(by: q, aroundTarget: from)
return lineNode
}
}
Sprout's (wow, the autocorrect will not allow me to actually type in his name!) post is indeed a solution, but I have implemented a very different solution in my code.
What I do is calculate the length of the line and the two endpoints, based on the X, Y and Z locations from the two ends:
let w = SCNVector3(x: CGFloat(x2m-x1m), y: CGFloat(y2m-y1m), z: CGFloat(z2m-z1m))
let l = w.length()
The length is simply pythag. Now I make an SCNNode that will hold the SCNCylinder, and position it in the middle of the line:
let node = SCNNode(geometry: cyl)
node.position = SCNVector3(x: CGFloat((x1m+x2m)/2.0), y: CGFloat((y1m+y2m)/2.0), z: CGFloat((z1m+z2m)/2.0))
And now the nasty part, where we calculate the Euler angles and rotate the node:
let lxz = (Double(w.x)**2 + Double(w.z)**2)**0.5
var pitch, pitchB: Double
if w.y < 0 {
pitchB = M_PI - asin(Double(lxz)/Double(l))
} else {
pitchB = asin(Double(lxz)/Double(l))
}
if w.z == 0 {
pitch = pitchB
} else {
pitch = sign(Double(w.z)) * pitchB
}
var yaw: Double
if w.x == 0 && w.z == 0 {
yaw = 0
} else {
let inner = Double(w.x) / (Double(l) * sin (pitch))
if inner > 1 {
yaw = M_PI_2
} else if inner < -1 {
yaw = M_PI_2
} else {
yaw = asin(inner)
}
}
node.eulerAngles = SCNVector3(CGFloat(pitch), CGFloat(yaw), 0)
I suspect there is a much simpler way to do this using one of the other rotation inputs, but this works and working is a feature!
Draw the line between two nodes:
func generateLine( startPoint: SCNVector3, endPoint: SCNVector3) -> SCNGeometry {
let vertices: [SCNVector3] = [startPoint, endPoint]
let data = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count) as Data
let vertexSource = SCNGeometrySource(data: data,
semantic: .vertex,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: 0,
dataStride: MemoryLayout<SCNVector3>.stride)
let indices: [Int32] = [ 0, 1]
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count) as Data
let element = SCNGeometryElement(data: indexData,
primitiveType: .line,
primitiveCount: indices.count/2,
bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [vertexSource], elements: [element])
}
How To Use
let line = generateLine(startPoint: SCNVector3Make(1, 1, 1), endPoint: SCNVector3Make(8, 8, 8))
let lineNode = SCNNode(geometry: line)
lineNode.position = SCNVector3Make(15, 15, 10)
scene.rootNode.addChildNode(lineNode)
The thickness of the line requires implementing the SCNSceneRendererDelegate, in particular:
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval){
glLineWidth(10)
}
Objective-C version of Winchill's answer:
-(void)lineNodeFrom:(SCNVector3)to to:(SCNVector3)from radius:(float)radius{
SCNVector3 w = SCNVector3Make(to.x - from.x, to.y - from.y, from.z - to.z);
float l = sqrtf(powf(w.x, 2) + powf(w.y, 2) + powf(w.z, 2.0f));
SCNCylinder * cylinder = [SCNCylinder cylinderWithRadius:radius height:l];
SCNMaterial * material = [SCNMaterial material];
material.diffuse.contents = [[UIColor darkGrayColor] colorWithAlphaComponent:0.75f];
cylinder.materials = #[material];
[self setGeometry:cylinder];
//original vector of cylinder above 0,0,0
SCNVector3 ov = SCNVector3Make(0, l/2.0,0);
//target vector, in new coordination
SCNVector3 nv = SCNVector3Make((from.x - to.x)/2.0, (from.y - to.y)/2.0, (from.z-to.z)/2.0);
// axis between two vector
SCNVector3 av = SCNVector3Make((ov.x + nv.x)/2.0, (ov.y+nv.y)/2.0, (ov.z+nv.z)/2.0);
//normalized axis vector
SCNVector3 av_normalized = [self normaliseVector:av];
float q0 = 0.0f; //cos(angel/2), angle is always 180 or M_PI
float q1 = av_normalized.x; // x' * sin(angle/2)
float q2 = av_normalized.y; // y' * sin(angle/2)
float q3 = av_normalized.z; // z' * sin(angle/2)
float r_m11 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3;
float r_m12 = 2 * q1 * q2 + 2 * q0 * q3;
float r_m13 = 2 * q1 * q3 - 2 * q0 * q2;
float r_m21 = 2 * q1 * q2 - 2 * q0 * q3;
float r_m22 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3;
float r_m23 = 2 * q2 * q3 + 2 * q0 * q1;
float r_m31 = 2 * q1 * q3 + 2 * q0 * q2;
float r_m32 = 2 * q2 * q3 - 2 * q0 * q1;
float r_m33 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
SCNMatrix4 transform;
transform.m11 = r_m11;
transform.m12 = r_m12;
transform.m13 = r_m13;
transform.m14 = 0.0;
transform.m21 = r_m21;
transform.m22 = r_m22;
transform.m23 = r_m23;
transform.m24 = 0.0;
transform.m31 = r_m31;
transform.m32 = r_m32;
transform.m33 = r_m33;
transform.m34 = 0.0;
transform.m41 = (to.x + from.x) / 2.0;
transform.m42 = (to.y + from.y) / 2.0;
transform.m43 = (to.z + from.z) / 2.0;
transform.m44 = 1.0;
self.transform = transform;
}
-(SCNVector3)normaliseVector:(SCNVector3)iv{
float length = sqrt(iv.x * iv.x + iv.y * iv.y + iv.z * iv.z);
if (length == 0){
return SCNVector3Make(0.0, 0.0, 0.0);
}
return SCNVector3Make(iv.x / length, iv.y / length, iv.z / length);
}
I'm trying to create a Toroid mesh, however I cant create the faces properly...
here is my code:
public static Group createToroidMesh(float radius, float tRadius, int tDivs, int rDivs) {
final Group root = new Group();
int numVerts = tDivs * rDivs;
float[] points = new float[numVerts * POINT_SIZE],
texCoords = new float[numVerts * TEXCOORD_SIZE];
int[] faces = new int[numVerts * FACE_SIZE],
smoothingGroups;
int pointIndex = 0, texIndex = 0, faceIndex = 0, smoothIndex = 0;
float tFrac = 1.0f / tDivs;
float rFrac = 1.0f / rDivs;
float x, y, z;
int p0 = 0, p1 = 0, p2 = 0, p3 = 0, t0 = 0, t1 = 0, t2 = 0, t3 = 0;
// create points
for (int vertIndex = 0; vertIndex < tDivs; vertIndex++) {
float radian = tFrac * vertIndex * 2.0f * 3.141592653589793f;
for (int crossSectionIndex = 0; crossSectionIndex < rDivs; crossSectionIndex++) {
float localRadian = rFrac * crossSectionIndex * 2.0f * 3.141592653589793f;
points[pointIndex] = x = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.cos(localRadian));
points[pointIndex + 1] = y = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.sin(localRadian));
points[pointIndex + 2] = z = (tRadius * (float) Math.sin(radian));
pointIndex += 3;
float r = crossSectionIndex < tDivs ? tFrac * crossSectionIndex * 2.0F * 3.141592653589793f : 0.0f;
texCoords[texIndex] = (0.5F + (float) (Math.sin(r) * 0.5D));;
texCoords[texIndex + 1] = ((float) (Math.cos(r) * 0.5D) + 0.5F);
texIndex += 2;
}
}
//create faces
for (int y1 = 0; y1 < tDivs - 1 ; y1++) {
float radian = tFrac * y1 * 2.0f * 3.141592653589793f;
for (int x1 = 0; x1 < rDivs - 1; x1++) {
float localRadian = rFrac * x1 * 2.0f * 3.141592653589793f;
p0 = y1 * rDivs + x1;
p1 = p0 + 1;
p2 = p0 + rDivs;
p3 = p2 + 1;
t0 = y1 * rDivs + x1;
t1 = t0 + 1;
t2 = t0 + rDivs;
t3 = t1 + 1;
try {
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p0);
faces[faceIndex + 3] = (t2);
faces[faceIndex + 4] = (p1);
faces[faceIndex + 5] = (t0);
faceIndex += FACE_SIZE;
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p1);
faces[faceIndex + 3] = (t0);
faces[faceIndex + 4] = (p3);
faces[faceIndex + 5] = (t1);
//faceIndex += FACE_SIZE;
} catch (Exception e) {
break;
}
}
}
TriangleMesh localTriangleMesh = new TriangleMesh();
localTriangleMesh.getPoints().setAll(points);
localTriangleMesh.getTexCoords().setAll(texCoords);
localTriangleMesh.getFaces().setAll(faces);
MeshView view = new MeshView(localTriangleMesh);
view.setMaterial(new PhongMaterial(Color.BLUEVIOLET));
view.setCullFace(CullFace.BACK);
root.getChildren().clear();
root.getChildren().add(view);
//return localTriangleMesh;
System.out.println("objs in group: " + root.getChildren().size()
+ ", \nnum of points in array: " + points.length / POINT_SIZE
+ ", \nnum TexCoords: " + texCoords.length / TEXCOORD_SIZE
+ ", \nface count: " + faces.length / FACE_SIZE);
root.setRotationAxis(Rotate.X_AXIS);
root.setRotate(90);
return root;
}
and here is the result:
can someone help me iterate through this a little better?
my constructor for the mesh is : Group verts = MeshUtils.createToroidMesh(100,15,4,4);
OK, I figured the problem out... The problem was in my faces loop. It was adding values outside the range of points, and texCoords, here is the corrected loop:
//create faces
for (int point = 0; point < (tubeDivisions) ; point++) {
for (int crossSection = 0; crossSection < (radiusDivisions) ; crossSection++) {
p0 = point * radiusDivisions + crossSection;
p1 = p0 >= 0 ? p0 + 1 : p0 - (radiusDivisions);
p1 = p1 % (radiusDivisions) != 0 ? p0 + 1 : p0 - (radiusDivisions - 1);
p2 = (p0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? p0 + radiusDivisions : p0 - (tubeDivisions * radiusDivisions) + radiusDivisions ;
p3 = p2 < ((tubeDivisions * radiusDivisions) - 1) ? p2 + 1 : p2 - (tubeDivisions * radiusDivisions) + 1;
p3 = p3 % (radiusDivisions) != 0 ? p2 + 1 : p2 - (radiusDivisions - 1);
t0 = point * (radiusDivisions) + crossSection;
t1 = t0 >= 0 ? t0 + 1 : t0 - (radiusDivisions);
t1 = t1 % (radiusDivisions) != 0 ? t0 + 1 : t0 - (radiusDivisions - 1);
t2 = (t0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? t0 + radiusDivisions : t0 - (tubeDivisions * radiusDivisions) + radiusDivisions ;
t3 = t2 < ((tubeDivisions * radiusDivisions) - 1) ? t2 + 1 : t2 - (tubeDivisions * radiusDivisions) + 1;
t3 = t3 % (radiusDivisions) != 0 ? t2 + 1 : t2 - (radiusDivisions - 1);
try {
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p0);
faces[faceIndex + 3] = (t2);
faces[faceIndex + 4] = (p1);
faces[faceIndex + 5] = (t0);
faceIndex += FACE_SIZE;
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p1);
faces[faceIndex + 3] = (t0);
faces[faceIndex + 4] = (p3);
faces[faceIndex + 5] = (t1);
faceIndex += FACE_SIZE;
} catch (Exception e) {
e.printStackTrace();
}
//System.out.println(" :: " +p0 + " : " + p1 + " : " + p2 + " : " + p3);
}
}
and the results: (square toroid)
(Round toroid)
Hope this can help if you struggle as well!
Please Note I did not assign UV textures as they should have been(if using Image material) since Only Color is used. TexCoords can be anything (0 - 1.0) if only using colors