Related
I am trying to dither an image. I have made some swift code which applies the floyd steinberg dither but it takes a long time to process an image as it isn't wrapped in a cifilter, its just swift code. I am thinking that if I can make a custom cifilter that it would be processed on the gpu and speed up the process. However I am not an expert in CIfilter language.
This is my swift code. I have written the error distribution matrix calculations out in full for the sake of clarity.
internal struct color {
let r: Int
let g: Int
let b: Int
}
func ditherImage2(){
let image = UIImage(named: "image")
let width = Int(image!.size.width)
let height = Int(image!.size.height)
let pixelArray = pixelarray(image)
func offset(row: Int, column: Int) -> Int {
return row * width + column
}
for y in 0 ..< height {
for x in 0 ..< width {
let currentOffset = offset(row: y, column: x)
let currentColor = pixelArray![currentOffset]
// get current colour of pixel
let oldR = currentColor.r
let oldG = currentColor.g
let oldB = currentColor.b
// quantize / reduce the colours to pallet of 6 colours
let factor = 1;
let newR = round(factor * oldR / 255) * (255/factor)
let newG = round(factor * oldG / 255) * (255/factor)
let newB = round(factor * oldB / 255) * (255/factor)
pixelArray[currentOffset] = color(r:newR, g:newG, b:newB)
let errR = oldR - newR;
let errG = oldG - newG;
let errB = oldB - newB;
// distribute the error to the surrounding pixels using floyd stenberg matrix
let index = offset(row:x+1, column:y)
let c = pixelArray[index]
let r = c.r
let g = c.g
let b = c.b
r = r + errR * 7/16.0;
g = g + errG * 7/16.0;
b = b + errB * 7/16.0;
pixelArray[index] = color(r:r, g:g, b:b);
let index2 = offset(row:x-1, column:y+1 );
let c2 = pixelArray[index2]
let r2 = c.r
let g2 = c.g
let b2 = c.b
r2 = r2 + errR * 3/16.0;
g2 = g2 + errG * 3/16.0;
b2 = b2 + errB * 3/16.0;
pixelArray[index] = color(r:r2, g:g2, b:b2);
let index3 = offset(row:x, column:y+1);
let c3 = pixelArray[index3]
let r3 = c.r
let g3 = c.g
let b3 = c.b
r3 = r3 + errR * 5/16.0;
g3 = g3 + errG * 5/16.0;
b3 = b3 + errB * 5/16.0;
pixelArray[index] = color(r:r3, g:g3, b:b3);
let index4 = offset(row:x+1, column:y+1);
let c4 = pixelArray[index]
let r4 = c.r
let g4 = c.g
let b4 = c.b
r4 = r4 + errR * 1/16.0;
g4 = g4 + errG * 1/16.0;
b4 = b4 + errB * 1/16.0;
pixelArray[index] = color(r:r4, g:g4, b:b4);
}
}
}
I Have found this https://github.com/rhoeper/Filterpedia-Swift4 which includes a custom filter for ordered dithering which I could use as a base and attempt to adapt to error diffusion dithering. I would prefer to find an existing custom kernel which does the job before jumping into learning CIfilter language. So I am wondering if anyone has an existing kernel or a link to one?
ordered dithering code
float orderedDither2x2(float colorin, float bx, float by, float errorIntensity)
{
float error = 0.0;
int px = int(bx);
int py = int(by);
if (py == 0) {
if (px == 0) { error = 1.0 / 4.0; }
if (px == 1) { error = 3.0 / 4.0; }
}
if (py == 1) {
if (px == 0) { error = 4.0 / 4.0; }
if (px == 1) { error = 2.0 / 4.0; }
}
return colorin * (error * errorIntensity);
}
kernel vec4 ditherBayer(sampler image, float intensity, float matrix, float palette)
{
vec4 pixel = sample(image, samplerCoord(image));
int msize = int(matrix);
float px = mod(pixel.x, msize >= 5 ? float(4.0) : float(msize));
float py = mod(pixel.y, msize >= 5 ? float(4.0) : float(msize));
float red = pixel.r;
float green = pixel.g;
float blue = pixel.b;
if (msize == 2) {
pixel.r = orderedDither2x2(red, px, py, intensity);
pixel.g = orderedDither2x2(green, px, py, intensity);
pixel.b = orderedDither2x2(blue, px, py, intensity);
}
if (msize == 3) {
pixel.r = orderedDither3x3(red, px, py, intensity);
pixel.g = orderedDither3x3(green, px, py, intensity);
pixel.b = orderedDither3x3(blue, px, py, intensity);
}
if (msize == 4) {
pixel.r = orderedDither4x4(red, px, py, intensity);
pixel.g = orderedDither4x4(green, px, py, intensity);
pixel.b = orderedDither4x4(blue, px, py, intensity);
}
if (msize >= 5) {
pixel.r = orderedDither8x8(red, px, py, intensity);
pixel.g = orderedDither8x8(green, px, py, intensity);
pixel.b = orderedDither8x8(blue, px, py, intensity);
}
if (int(palette) == 0) { return vec4(binary(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
if (int(palette) == 1) { return vec4(commodore64(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
if (int(palette) == 2) { return vec4(vic20(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
if (int(palette) == 3) { return vec4(appleII(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
if (int(palette) == 4) { return vec4(zxSpectrumBright(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
if (int(palette) == 5) { return vec4(zxSpectrumDim(vec3(pixel.r, pixel.g, pixel.b)), pixel.a); }
return pixel;
}
The problem with Floyd-Steinberg dithering is that it's a serial algorithm – the color value of a result pixel depends on pixels that were previously computed. Core Image (and any kind of SIMD parallelization technique) is not very well suited for these kinds of problems. They are designed to perform the same task on all pixels concurrently.
However, I found some approaches for partially parallelizing the computation of independent pixels on the GPU and even an interesting CPU-GPU-hybrid approach.
Unfortunately, Core Image is probably not the best framework for implementing those techniques since CIFilters are limited in what GPU resources they can leverage (no access to global memory, for example). You could instead use Metal compute shaders directly (instead of through Core Image), which will require a lot more support code, though.
If you don't necessarily need error diffusion, you could still use ordered dithering (which can be highly parallelized) to achieve similar results. I also found a nice article about that. The built-in CIDither filter is probably also using this approach.
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?
I have a ball, and I want to make it move in a direction by applying a force.
In my game, I have the 2 points that form a line that I want the ball to follow.
How can I know the vector components dx and dy?
I only know the (y2 - y1) / (x2 - x1)
EDIT ::
let ddx = (bullet.position.x - position.x)
let ddy = (bullet.position.y - position.y)
let Len = sqrt((ddx * ddx) + (ddy * ddy))
let dx = ddx / Len
let dy = ddy / Len
let force = CGVector(dx: dx, dy: dy)
let action = SKAction.applyForce(force, at: position, duration: 2)
let fire = SKAction.repeatForever(action)
bullet.run(fire, withKey: "firing\(bullet.name)")
still nothing happens
update ::
It worked by this
let action = SKAction.moveBy(x: bullet.position.x, y: bullet.position.y, duration: 2)
Seems you need components of unit direction vector:
ddx = (x2 - x1)
ddy = (y2 - y1)
Len = Sqrt(ddx * ddx + ddy * ddy)
dx = ddx / Len
dy = ddy / Len
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 having a bit of a problem calculating the arc-length of my bezier and b-spline curves. I've been banging my head against this for several days, and I think I'm almost there, but can't seem to get it exactly right. I'm developing in Swift, but I think its syntax is clear enough that anyone who knows C/C++ would be able to read it. If not, please let me know and I'll try to translate it into C/C++.
I've checked my implementations against several sources over and over again, and, as far as the algorithms go, they seem to be correct, although I'm not so sure about the B-spline algorithm. Some tutorials use the degree, and some use the order, of the curve in their calculations, and I get really confused. In addition, in using the Gauss-Legendre quadrature, I understand that I'm supposed to sum the integration of the spans, but I'm not sure I'm understanding how to do that correctly. From what I understand, I should be integrating over each knot span. Is that correct?
When I calculate the length of a Bezier curve with the following control polygon, I get 28.2842712474619, while 3D software (Cinema 4D and Maya) tells me the length should be 30.871.
let bezierControlPoints = [
Vector(-10.0, -10.0),
Vector(0.0, -10.0),
Vector(0.0, 10.0),
Vector(10.0, 10.0)
]
The length of the b-spline is similarly off. My algorithm produces 5.6062782185353, while it should be 7.437.
let splineControlPoints = [
Vector(-2.0, -1.0),
Vector(-1.0, 1.0),
Vector(-0.25, 1.0),
Vector(0.25, -1.0),
Vector(1.0, -1.0),
Vector(2.0, 1.0)
]
I'm not a mathematician, so I'm struggling with the math, but I think I have the gist of it.
The Vector class is pretty straight-forwared, but I've overloaded some operators for convenience/legibility which makes the code quite lengthy, so I'm not posting it here. I'm also not including the Gauss-Legendre weights and abscissae. You can download the source and Xcode project from here (53K).
Here's my bezier curve class:
class Bezier
{
var c0:Vector
var c1:Vector
var c2:Vector
var c3:Vector
init(ic0 _ic0:Vector, ic1 _ic1:Vector, ic2 _ic2:Vector, ic3 _ic3:Vector) {
c0 = _ic0
c1 = _ic1
c2 = _ic2
c3 = _ic3
}
// Calculate curve length using Gauss-Legendre quadrature
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 3 // Good enough for a quadratic polynomial
let xprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dx(atTime:t) })
let yprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dy(atTime:t) })
return sqrt(xprime*xprime + yprime*yprime)
}
// I could vectorize this, but correctness > efficiency
// The derivative of the x-component
func dx(atTime t:Double)->Double {
let tc = (1.0-t)
let r0 = (3.0 * tc*tc) * (c1.x - c0.x)
let r1 = (6.0 * tc*t) * (c2.x - c1.x)
let r2 = (3.0 * t*t) * (c3.x - c2.x)
return r0 + r1 + r2
}
// The derivative of the y-component
func dy(atTime t:Double)->Double {
let tc = (1.0-t)
let r0 = (3.0 * tc*tc) * (c1.y - c0.y)
let r1 = (6.0 * tc*t) * (c2.y - c1.y)
let r2 = (3.0 * t*t) * (c3.y - c2.y)
return r0 + r1 + r2
}
}
Here is my b-spline class:
class BSpline
{
var spanLengths:[Double]! = nil
var totalLength:Double = 0.0
var cp:[Vector]
var knots:[Double]! = nil
var o:Int = 4
init(controlPoints:[Vector]) {
cp = controlPoints
calcKnots()
}
// Method to return length of the curve using Gauss-Legendre numerical integration
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = o-1 // The derivative should be quadratic, so o-2 would suffice?
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let xprime = gl.integrate(a:t0, b:t1, closure:self.dx)
let yprime = gl.integrate(a:t0, b:t1, closure:self.dy)
let spanLength = sqrt(xprime*xprime + yprime*yprime)
spanLengths.append(spanLength)
totalLength += spanLength
}
}
// The b-spline basis function
func basis(i:Int, _ k:Int, _ x:Double)->Double {
var r:Double = 0.0
switch k {
case 0:
if (knots[i] <= x) && (x <= knots[i+1]) {
r = 1.0
} else {
r = 0.0
}
default:
var n0 = x - knots[i]
var d0 = knots[i+k]-knots[i]
var b0 = basis(i,k-1,x)
var n1 = knots[i+k+1] - x
var d1 = knots[i+k+1]-knots[i+1]
var b1 = basis(i+1,k-1,x)
var left = Double(0.0)
var right = Double(0.0)
if b0 != 0 && d0 != 0 { left = n0 * b0 / d0 }
if b1 != 0 && d1 != 0 { right = n1 * b1 / d1 }
r = left + right
}
return r
}
// Method to calculate and store the knot vector
func calcKnots() {
// The number of knots in the knot vector = number of control points + order (i.e. degree + 1)
let knotCount = cp.count + o
knots = [Double]()
// For an open b-spline where the ends are incident on the first and last control points,
// the first o knots are the same and the last o knots are the same, where o is the order
// of the curve.
var k = 0
for i in 0 ..< o {
knots.append(0.0)
}
for i in o ..< cp.count {
k++
knots.append(Double(k))
}
k++
for i in cp.count ..< knotCount {
knots.append(Double(k))
}
}
// I could vectorize this, but correctness > efficiency
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
}
And here is my Gauss-Legendre implementation:
class GaussLegendre
{
var order:Int = 5
init() {
}
// Numerical integration of arbitrary function
func integrate(a _a:Double, b _b:Double, closure f:(Double)->Double)->Double {
var result = 0.0
let wgts = gl_weights[order-2]
let absc = gl_abscissae[order-2]
for i in 0..<order {
let a0 = absc[i]
let w0 = wgts[i]
result += w0 * f(0.5 * (_b + _a + a0 * (_b - _a)))
}
return 0.5 * (_b - _a) * result
}
}
And my main logic:
let bezierControlPoints = [
Vector(-10.0, -10.0),
Vector(0.0, -10.0),
Vector(0.0, 10.0),
Vector(10.0, 10.0)
]
let splineControlPoints = [
Vector(-2.0, -1.0),
Vector(-1.0, 1.0),
Vector(-0.25, 1.0),
Vector(0.25, -1.0),
Vector(1.0, -1.0),
Vector(2.0, 1.0)
]
var bezier = Bezier(controlPoints:bezierControlPoints)
println("Bezier curve length: \(bezier.curveLength())\n")
var spline:BSpline = BSpline(controlPoints:splineControlPoints)
spline.cacheSpanLengths()
println("B-Spline curve length: \(spline.totalLength)\n")
UPDATE: PROBLEM (PARTIALLY) SOLVED
Thanks to Mike for his answer!
I verified that I am correctly remapping the numerical integration from the interval a..b to -1..1 for the purposes of Legendre-Gauss quadrature. The math is here (apologies to any real mathematicians out there, it's the best I could do with my long-forgotten calculus).
I've increased the order of the Legendre-Gauss quadrature from 5 to 32 as Mike suggested.
Then after a lot of floundering around in Mathematica, I came back and re-read Mike's code and discovered that my code was NOT equivalent to his.
I was taking the square root of the sums of the squared integrals of the derivative components:
when I should have been taking the integral of the magnitudes of the derivative vectors:
In terms of code, in my Bezier class, instead of this:
// INCORRECT
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 3 // Good enough for a quadratic polynomial
let xprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dx(atTime:t) })
let yprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dy(atTime:t) })
return sqrt(xprime*xprime + yprime*yprime)
}
I should have written this:
// CORRECT
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 32
return = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in
let x = self.dx(atTime:t)
let y = self.dy(atTime:t)
return sqrt(x*x + y*y)
})
}
My code calculates the arc length as: 3.59835872777095
Mathematica: 3.598358727834686
So, my result is pretty close. Interestingly, there is a discrepancy between a plot in Mathematica of my test Bezier curve, and the same rendered by Cinema 4D, which would explain why the arc lengths calculated by Mathematica and Cinema 4D are different as well. I think I trust Mathematica to be more correct, though.
In my B-Spline class, instead of this:
// INCORRECT
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = o-1 // The derivative should be quadratic, so o-2 would suffice?
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let xprime = gl.integrate(a:t0, b:t1, closure:self.dx)
let yprime = gl.integrate(a:t0, b:t1, closure:self.dy)
let spanLength = sqrt(xprime*xprime + yprime*yprime)
spanLengths.append(spanLength)
totalLength += spanLength
}
}
I should have written this:
// CORRECT
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = 32
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let spanLength = gl.integrate(a:t0, b:t1, closure:{ (t:Double)->Double in
let x = self.dx(atTime:t)
let y = self.dy(atTime:t)
return sqrt(x*x + y*y)
})
spanLengths.append(spanLength)
totalLength += spanLength
}
}
Unfortunately, the B-Spline math is not as straight-forward, and I haven't been able to test it in Mathematica as easily as the Bezier math, so I'm not entirely sure my code is working, even with the above changes. I will post another update when I verify it.
UPDATE 2: PROBLEM SOLVED
Eureka, I discovered an off-by one error in my code to calculate the B-Spline derivative.
Instead of
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o // INCORRECT (should be one less)
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o // INCORRECT (should be one less_
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
I should have written
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o-1 // CORRECT
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o-1 // CORRECT
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
My code now calculates the length of the B-Spline curve as 6.87309971722132.
Mathematica: 6.87309884638438.
It's probably not scientifically precise, but good enough for me.
The Legendre-Gauss procedure is specifically defined for the interval [-1,1], whereas Beziers and B-Splines are defined over [0,1], so that's a simple conversion and at least while you're trying to make sure your code does the right thing, easy to bake in instead of supplying a dynamic interval (as you say, accuracy over efficiency. Once it works, we can worry about optimising)
So, given weights W and abscissae A (both of same length n), you'd do:
z = 0.5
for i in 1..n
w = W[i]
a = A[i]
t = z * a + z
sum += w * arcfn(t, xpoints, ypoints)
return z * sum
with the pseudo-code assuming list indexing from 1. The arcfn would be defined as:
arcfn(t, xpoints, ypoints):
x = derive(xpoints, t)
y = derive(ypoints, t)
c = x*x + y*y
return sqrt(c)
But that part looks right already.
Your derivatives look correct too, so the main question is: "are you using enough slices in your Legendre-Gauss quadrature?". Your code suggests you're using only 5 slices, which isn't nearly enough to get a good result. Using http://pomax.github.io/bezierinfo/legendre-gauss.html as term data, you generally want a set for n of 16 or higher (for cubic Bezier curves, 24 is generally safe, although still underperformant for curves with cusps or lots of inflections).
I can recommend taking the "unit test" approach here: test your bezier and bspline code (separately) for known base and derivative values. Do those check out? One problem ruled out. On to your LG code: if you perform Legendre-Gauss on a parametric function for a straight line using:
fx(t) = t
fy(t) = t
fx'(t) = 1
fy'(t) = 1
over interval t=[0,1], we know the length should be exactly the square root of 2, and the derivatives are the simplest possible. If those work, do a non-linear test using:
fx(t) = sin(t)
fy(t) = cos(t)
fx'(t) = cos(t)
fy'(t) = -sin(t)
over interval t=[0,1]; we know the length should be exactly 1. Does your LG implementation yield the correct value? Another problem ruled out. If it doesn't, check your weights and abscissae. Do they match the ones from the linked page (generated with a verifiably correct Mathematica program, so pretty much guaranteed to be correct)? Are you using enough slices? Bump the number up to 10, 16, 24, 32; increasing the number of slices will show a stabilising summation, where adding more slices doesn't change digits before the 2nd, 3rd, 4th, 5th, etc decimal point as you increase the count.
Are the curves you're testing with known to be problematic curves? Plot them, do they have cusps or lots of inflections? That's going to be a problem for LG, try simpler curves to see if the values you get back for those, at least, are correct.
Finally, check your types: Are you using the highest precision possible datatype? 32 bit floats are going to run into mysteriously disappearing FPU and wonderful rounding errors at the values we need to use when doing LG with a reasonable number of slices.