Set Color of Custom Geometry in SceneKit - scenekit

I've successfully built and displayed a custom geometry (a cube) in SceneKit from this post
Custom Geometry
Now, I want to set the color of each face to a different color. I found this post that supposed to do that
Per Vertex Color
Unfortunately, I can't seem to set the color of a face of the cube to a specific color. For example, if I set the color of all vertices to SCNVector3(x:1,y:0,z:0), I would expect all faces to be red, but, instead, they're all green. Setting the colors to SCNVector3(x:0,y:1,z:0) turns the faces black. Here's the relevant code
let colors = [SCNVector3](count:vertices.count, repeatedValue:SCNVector3(x:1, y:0, z:0))
let colorData = NSData(bytes: colors, length: sizeof(SCNVector3) * colors.count)
let colorSource = SCNGeometrySource(data: colorData, semantic: SCNGeometrySourceSemanticColor, vectorCount: colors.count, floatComponents: true, componentsPerVector: 3, bytesPerComponent: sizeof(Float), dataOffset: 0, dataStride: sizeof(SCNVector3))
let geometry = SCNGeometry(sources: [colorSource, vertexSource, normalSource], elements: [element])
// Create a node and assign our custom geometry
let node = SCNNode()
let material = SCNMaterial()
material.diffuse.contents = NSColor.whiteColor()
geometry.materials = [material]
node.geometry = geometry
Does anyone know why it's not working?

You have to create a material per face with a different color.
For example:
let material1 = SCNMaterial()
material1.diffuse.contents = NSColor.whiteColor()
let material2 = SCNMaterial()
material2.diffuse.contents = NSColor.greenColor()
let material3 = SCNMaterial()
material3.diffuse.contents = NSColor.redColor()
let material4 = SCNMaterial()
material4.diffuse.contents = NSColor.blackColor()
let material5 = SCNMaterial()
material5.diffuse.contents = NSColor.blueColor()
let material6 = SCNMaterial()
material6.diffuse.contents = NSColor.whiteColor()
geometry.materials = [material1,material2,material3,material4,material5,material6]

Related

Graphic Points are not working outside of viewDidLoad

I've created a Graphic Class to show for user a graphic points choose accordingly with user information.
If I use the graphicView object and add points at viewDidLoad, the graphic is presented correctly, if not there, the graphic presents no data.
See below the code for the Graphics and the code when I am requesting to mark the points.
class GraphView: UIView {
private struct Constants {
static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
static let margin: CGFloat = 40.0
static let topBorder: CGFloat = 30
static let bottomBorder: CGFloat = 40
static let colorAlpha: CGFloat = 0.3
static let circleDiameter: CGFloat = 5.0
}
//1 - the properties for the gradient
var startColor: UIColor = UIColor.rgb(red: 14, green: 40, blue: 80)
var endColor: UIColor = UIColor.rgb(red: 14, green: 40, blue: 80)
//Weekly sample data
var graphPoints: [Int] = [0]
override func draw(_ rect: CGRect) {
let width = rect.width
let height = rect.height
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: Constants.cornerRadiusSize)
path.addClip()
//2 - get the current context
let context = UIGraphicsGetCurrentContext()!
let colors = [startColor.cgColor, endColor.cgColor]
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
//4 - set up the color stops
let colorLocations: [CGFloat] = [0.0, 1.0]
//5 - create the gradient
let gradient = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations)!
//6 - draw the gradient
var startPoint = CGPoint.zero
var endPoint = CGPoint(x: 0, y: self.bounds.height)
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: CGGradientDrawingOptions(rawValue: 0))
//calculate the x point
let margin = Constants.margin
let columnXPoint = { (column:Int) -> CGFloat in
//Calculate gap between points
let spacer = (width - margin * 2 - 4) / CGFloat((self.graphPoints.count - 1))
var x: CGFloat = CGFloat(column) * spacer
x += margin + 2
return x
}
// calculate the y point
let topBorder: CGFloat = Constants.topBorder
let bottomBorder: CGFloat = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
//set up the points line
let graphPath = UIBezierPath()
//go to start of line
graphPath.move(to: CGPoint(x:columnXPoint(0), y:columnYPoint(graphPoints[0])))
//add points for each item in the graphPoints array
//at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
context.saveGState()
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLine(to: CGPoint(x: columnXPoint(graphPoints.count - 1), y:height))
clippingPath.addLine(to: CGPoint(x:columnXPoint(0), y:height))
clippingPath.close()
//4 - add the clipping path to the context
clippingPath.addClip()
let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
//draw the line on top of the clipped gradient
graphPath.lineWidth = 3.0
graphPath.stroke()
//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(ovalIn: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
circle.fill()
}
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.move(to: CGPoint(x:margin, y: topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y:topBorder))
//center line
linePath.move(to: CGPoint(x:margin, y: graphHeight/2 + topBorder))
linePath.addLine(to: CGPoint(x:width - margin, y:graphHeight/2 + topBorder))
//bottom line
linePath.move(to: CGPoint(x:margin, y:height - bottomBorder))
linePath.addLine(to: CGPoint(x:width - margin, y:height - bottomBorder))
let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
}
}
I am trying to mark the points accordingly with the user input - see below:
func budgetAvailableCalculationFunction() {
let bankValue = (userSalary as NSString).integerValue
let bankPorcentage: Int = 100
let expenses = (userExpenses as NSString).integerValue
let calculation1: Int = expenses * bankPorcentage
let calculation2: Int = calculation1 / bankValue
let cashPorcentageAvailable = calculation2
let value: [Int] = [expenses]
self.setupGraphy(points: value)
progressView.progress = 0.0
progress.completedUnitCount = Int64(cashPorcentageAvailable)
progressView.setProgress(Float(self.progress.fractionCompleted), animated: true)
progressView.progressTintColor = UIColor.rgb(red: 239, green: 75, blue: 92)
progressView.backgroundColor = UIColor.rgb(red: 239, green: 75, blue: 92)
progressView.trackTintColor = .white
progressView.clipsToBounds = false
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.layer.cornerRadius = 0
porcentageLabelForMonth.text = "\(Int(self.progress.fractionCompleted * 100)) %"
}
The setupGraphy is just a function that returns an array of indexes that the user adds it.
Please note that using the same function at the viewDidLoad works:
self.setupGraphy(points: [100, 400, 200, 250])
enter image description here
Anyone?
Code for viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.rgb(red: 245, green: 246, blue: 250)
tableView.delegate = self
tableView.dataSource = self
tableView.register(LastTransactionsCell.self, forCellReuseIdentifier: "LastTransactionsCell")
tableView.backgroundColor = .clear
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy - LLLL"
let nameOfMonth = dateFormatter.string(from: now)
currentMonthLabel.text = nameOfMonth
setupUIXViews()
fetchUserInfo()
//static data for now
*self.setupGraphy(points: [100, 400, 200, 250])*
DispatchQueue.main.asyncAfter(deadline: .now() + 2){
self.budgetAvailableCalculationFunction()
}
}
The way I managed to fix (seems like a temporary fix) is by creating a new Int variable at the top of the project and passing the fetch data from the DB to these variables. That way I can use them at viewDidLoad instead of inside the fetch function.

How to integrate two scales for single Y-axis

anychart.theme(anychart.themes.darkEarth);
anychart.onDocumentReady(function () {
var dataSet = anychart.data.set([
['P1', 969.5, 2040, 1200, 1600,2000],
['P2', 779.1, 1794, 1124, 1724,4000],
['P3', 739.2, 2026, 1006, 1806,5000]
]);
var seriesData_1 = dataSet.mapAs({x: [0], value: [1]});
var seriesData_2 = dataSet.mapAs({x: [0], value: [2]});
var seriesData_3 = dataSet.mapAs({x: [0], value: [3]});
var seriesData_4 = dataSet.mapAs({x: [0], value: [4]});
var seriesData_5 = dataSet.mapAs({x: [0], value: [5]});
chart = anychart.column();
chart.animation(true);
chart.container('container');
chart.title('Combination of Stacked Column and Line Chart (Dual Y-Axis)');
var scale = anychart.scales.linear();
scale.stackMode('value');
chart.column(seriesData_2);
var lineSeries = chart.area(seriesData_1);
lineSeries.yScale(scale);
var lineSeries2 = chart.area(seriesData_5);
lineSeries2.yScale(scale);
chart.column(seriesData_3);
chart.column(seriesData_4);
chart.draw();
});
I am trying to add a Series Column chart with Stacked Area.
But the Series Column and Stacked Area should have different Scales to make them separate from Stacking.
How to integrate these two Scales into the Yaxis.
Is there any to do that? Please do suggest!!
I think you can use additional axis from the left or from the right of the chart.
Left axis example:
var secondYAxis = chart.yAxis(1).scale(scale);
http://jsfiddle.net/cgquc1pz/1/
Right axis example:
var secondYAxis = chart.yAxis(1).scale(scale);
secondYAxis.orientation('right');
http://jsfiddle.net/cgquc1pz/2/

Using Vuforia provided Projection Matrix and Marker Pose in SceneKit

Currently I'm trying to troubleshoot the use of the projection matrix and framemarker pose when rendering in SceneKit. The model in the scene and the camera image background appear without problems. However, once I change the projection matrix and framemarker pose matrix to match Vuforia everything is pushed offscreen.
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
let camera = self.cameraNode?.camera
camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4Invert(SCNMatrix4FromMat4(framemarker.pose))
self.objectNode?.transform = pose
}
}
Is this a correct way to setup the SCNCamera and object node and is there anything else required to setup Vuforia framemarkers to work with SceneKit?
It just works!
The hard part is determining what pieces of SceneKit are necessary to make this work. Originally I read the article Making Augmented Reality app easily with Scenekit + Vuforia which outlined how to rejigger the sample app for user-defined targets. The downsides to that article include that it isn't always clear what the author changed, no sample project is provided, and it is based upon an older version of Vuforia. Ultimately, I found it unnecessary to invert the pose matrix.
Draw camera image and set projection matrix and update marker pose
override func viewDidLoad()
{
super.viewDidLoad()
let scene = SmartScanScene()
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
scene.rootNode.addChildNode(cameraNode)
_cameraNode = cameraNode
let view = self.view as! SCNView
view.backgroundColor = UIColor.blackColor()
view.showsStatistics = true
// view.debugOptions = SCNDebugOptions.ShowBoundingBoxes.union(.ShowWireframe)
view.autoenablesDefaultLighting = true
view.allowsCameraControl = false
}
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
_cameraNode?.camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4FromMat4(framemarker.pose)
self.objectNode?.transform = pose
}
}
func didUpdateCameraImage(image: UIImage?)
{
if let image = image {
_scene?.background.contents = image
}
}

Get size of dae node

Suppose I have a collada file which has a box in it and I import the dae file into the scene. Now after importing in the scene I know the dae object is a box. How can I get the dimensions of the box in scenekit after adding it to the scene
If I import the node as a SCNBox i get errors saying that SNCBox is not a subtype of SCNNode.
floorNode = scene.rootNode.childNodeWithName("floor", recursively: false) as SCNBox
floorNode?.physicsBody = SCNPhysicsBody.staticBody()
floorNode?.physicsBody?.categoryBitMask = 2
let floorGeo: SCNBox = floorNode.geometry! as SCNBox
How do I get the dimensions if SCNNode is the only way to import nodes?
SCNBox is just helper subclass of SCNGeometry for box geometry creation. When you import Collada into a scene, you get scene graph of SCNNodes with SCNGeometries, not SCNBox'es or SCNCones etc doesn't matter how they look. If you want to get dimensions of any node you should use SCNBoundingVolume protocol, which is implemented by both SCNNode and SCNGeometry classes:
func getBoundingBoxMin(_ min: UnsafeMutablePointer,
max max: UnsafeMutablePointer) -> Bool
With this method you will get bounding box corners.
For box-shaped geometry, dimensions will match bounding box.
Example:
var v1 = SCNVector3(x:0, y:0, z:0)
var v2 = SCNVector3(x:0, y:0, z:0)
node.getBoundingBoxMin(&v1, max:&v2)
Where node is node you want to get bounding box of.
Results will be in v1 and v2.
Swift 3
Using Swift 3 you can simply use node.boundingBox.min and node.boundingBox.max respectively.
Example Swift code on how to use boundingBox:
var min = shipNode.boundingBox.min
var max = shipNode.boundingBox.max
let w = CGFloat(max.x - min.x)
let h = CGFloat(max.y - min.y)
let l = CGFloat(max.z - min.z)
let boxShape = SCNBox (width: w , height: h , length: l, chamferRadius: 0.0)
let shape = SCNPhysicsShape(geometry: boxShape, options: nil)
shipNode.physicsBody!.physicsShape = SCNPhysicsShape(geometry: boxShape, options: nil)
shipNode.physicsBody = SCNPhysicsBody.dynamic()
Swift 5.4.
import ARKit
extension SCNNode {
var height: CGFloat { CGFloat(self.boundingBox.max.y - self.boundingBox.min.y) }
var width: CGFloat { CGFloat(self.boundingBox.max.x - self.boundingBox.min.x) }
var length: CGFloat { CGFloat(self.boundingBox.max.z - self.boundingBox.min.z) }
var halfCGHeight: CGFloat { height / 2.0 }
var halfHeight: Float { Float(height / 2.0) }
var halfScaledHeight: Float { halfHeight * self.scale.y }
}
// Usage:
let node = SCNNode()
node.height
node.width
node.lenght

when dynamic body falls down, its `_ball.position.y` value doesn't change

I have pretty simple question -
func setupScene() {
let sceneView = view as SCNView
sceneView.backgroundColor = SKColor.blackColor()
_scene = SCNScene()
sceneView.scene = _scene
sceneView.showsStatistics = true
// Scene gravity
sceneView.scene?.physicsWorld.gravity = SCNVector3Make(0, -70, 0)
sceneView.scene?.physicsWorld.speed = 2.0
sceneView.delegate = self
}
At some point of time have spawnBall method:
func spawnBall() {
_ball = SCNNode(geometry: SCNSphere(radius: 15))
_ball.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "ball")
_ball.geometry?.firstMaterial?.emission.contents = UIImage(named: "ball")
_ball.geometry?.firstMaterial?.emission.intensity = 0
_ball.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Dynamic, shape: nil)
_ball.physicsBody?.restitution = 0.1
_ball.physicsBody?.angularVelocity = SCNVector4(x: 1, y: 1, z: 1, w: 1)
let position = SCNVector3Make(0, 15*10, -200)
_ball.position = position
_scene.rootNode.addChildNode(_ball)
}
The question is - why when dynamic body falls down, the it's _ball.position.y value doesn't change?
I used to UIKit when you move UIView - it's origin changes (I was assuming to see something similar, coz I set _ball.position.y = 150 and it should go down)
And how to observe it's movement?
the position of its presentationNode will change. The model values are left untouched by the physics engine.

Resources