I created a SCNNode and added an Audio to it.
It is a Mono audio. Everything is set up correctly.
It is working as Spatial Audio, that's not the problem.
The problem is that as i get closer or far away it barely changes the volume. I know it changes if i get very very far away, but it's nothing like Apple demonstrated here:
https://youtu.be/d9kb1LfNNU4?t=23
Some other games i see the audio volume really changing from one step distance.
With mine, with one step you can't even tell the volume changed. You need at least 4 steps.
Anyone has any clue why?
Code bellow:
SCNNode *audioNode = [[SCNNode alloc] init];
SCNAudioSource *audioSource = [[SCNAudioSource alloc] initWithFileNamed:audioFileName];
audioSource.loops = YES;
[audioSource load];
audioSource.volume = 0.05; // <-- i used different values. won't change much either
audioSource.positional = YES;
//audioSource.shouldStream = NO; // <-- makes no difference
[audioNode addAudioPlayer:[SCNAudioPlayer audioPlayerWithSource:audioSource]];
[audioNode runAction:[SCNAction playAudioSource:audioSource waitForCompletion:NO] completionHandler:nil];
[massNode addChildNode:audioNode];
Maybe scale of the nodes?
The whole scene is the size of around 4 feet.
When i add an object i usually scale it to 0.005 (otherwise it gets way too big).
But i also tried with one that was already in the right size from .scn file.
It shouldn't affect anything tho, since the result is a coffee table size scene and i can see the objects alright.
Updated.
Here's a working code for controlling sound's decay (works in iOS and macOS):
import AVFoundation
import ARKit
class ViewController: UIViewController, AVAudioMixing {
#IBOutlet var sceneView: SCNView!
// #IBOutlet var sceneView: ARSCNView!
func destination(forMixer mixer: AVAudioNode,
bus: AVAudioNodeBus) -> AVAudioMixingDestination? {
return nil
}
var volume: Float = 0.0
var pan: Float = 0.0
var sourceMode: AVAudio3DMixingSourceMode = .bypass
var pointSourceInHeadMode: AVAudio3DMixingPointSourceInHeadMode = .bypass
var renderingAlgorithm = AVAudio3DMixingRenderingAlgorithm.sphericalHead
var rate: Float = 1.2
var reverbBlend: Float = 40.0
var obstruction: Float = -100.0
var occlusion: Float = -100.0
var position = AVAudio3DPoint(x: 0, y: 0, z: 10)
let audioNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let myScene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 0)
myScene.rootNode.addChildNode(cameraNode)
// let sceneView = view as! SCNView
sceneView.scene = myScene
sceneView.backgroundColor = UIColor.orange
let myPath = Bundle.main.path(forResource: "Mono_Audio", ofType: "mp3")
let myURL = URL(fileURLWithPath: myPath!)
let mySource = SCNAudioSource(url: myURL)!
mySource.loops = true
mySource.isPositional = true // Positional Audio
mySource.shouldStream = false // FALSE for Positional Audio
mySource.volume = volume
mySource.reverbBlend = reverbBlend
mySource.rate = rate
mySource.load()
let player = SCNAudioPlayer(source: mySource)
let sphere: SCNGeometry = SCNSphere(radius: 0.1)
let sphereNode = SCNNode(geometry: sphere)
sphereNode.addChildNode(audioNode)
myScene.rootNode.addChildNode(sphereNode)
audioNode.addAudioPlayer(player)
sceneView.audioEnvironmentNode.distanceAttenuationParameters.maximumDistance = 2
sceneView.audioEnvironmentNode.distanceAttenuationParameters.referenceDistance = 0.1
sceneView.audioEnvironmentNode.renderingAlgorithm = .auto
// sceneView.audioEnvironmentNode.reverbParameters.enable = true
// sceneView.audioEnvironmentNode.reverbParameters.loadFactoryReverbPreset(.plate)
let hither = SCNAction.moveBy(x: 0, y: 0, z: 1, duration: 2)
let thither = SCNAction.moveBy(x: 0, y: 0, z: -1, duration: 2)
let sequence = SCNAction.sequence([hither, thither])
let loop = SCNAction.repeatForever(sequence)
sphereNode.runAction(loop)
}
}
And, yes, you're absolutely right – there are some obligatory settings.
But there are 7 of them:
use AVAudioMixing protocol with its stubs (properties and methods).
use MONO audio file.
use source.isPositional = true.
use source.shouldStream = false.
assign maximumDistance value to distanceAttenuationParameters property.
assign referenceDistance value to distanceAttenuationParameters property.
and location of mySource.load() is very important in your code.
P.S. If the aforementioned tips didn't help you, then use additional instance properties to make your sound even quieter using a graph, obstacles and orientation of implicit listener:
var rolloffFactor: Float { get set } // attenuation's graph, default = 1
var obstruction: Float { get set } // default = 0.0
var occlusion: Float { get set } // default = 0.0
var listenerAngularOrientation: AVAudio3DAngularOrientation { get set } //(0,0,0)
It definitely works if you'll write it in Objective-C.
In this example the distance of audioNode is 1 meter away from a listener.
If none of the above answers seem to work, try the following code:
sceneView.audioEnvironmentNode.reverbParameters.enable = true
And if even these seem to barely work, or if you wanna optimal performance, there is a property called level where you can set the level of how spatial the code can be.
sceneView.audioEnvironmentNode.reverbParameters.level = 40
(the level of the reverbParameters ranges between -40 to 40 parameters)
I'm making a bidirectional path tracer and I have some troubles.
To be clear :
1) One point light
2) All objects are diffuse
3) All objects are spheres, even walls (they are very large)
4) NO MIS WEIGHTING
The light emission is a 3D vector. The BRDF of a sphere is a 3D vector. Hard coded.
In the main function below I generate EyePath and LightPath then I connect them. At least I try.
In this post I will talking about the main function then EyePath then LightPath. The talking about connecting function will appear once EyePath and Light are good.
First questions :
Does the generation of the first light point is good ?
Do I need to compute this point according to the emission of the light source? or is it just the emission ? The line is commented where i'm filling the Vertices structure.
Do I need to translate fromlight ? In order to put it on the sphere
The code below is sampled in the main function. Above it there is two for loops going through all pixels. Camera.o is the eye. CameraRayDir is the direction to the current pixel.
//The path light starting point is at the same position as the light
Ray fromLight(Vec(0, 24.3, 0), Vec());
Sphere light = spheres[7];
#define PDF 0.15915494309 // 1 / (2 * PI)
for(int i = 0; i < samps; ++i)
{
std::vector<Vertices> PathEye;
std::vector<Vertices> PathLight;
Vec cameraRayDir = cx * (double(x) / w - .5) + cy * (double(y) / h - .5) + camera.d;
Ray rayEye(camera.o, cameraRayDir.norm());
// Hemisphere oriented towards the top
fromLight.d = generateRayInHemisphere(fromLight.o,Vec(0,1,0)).d;
double f = clamp(n.dot(fromLight.d.norm()));
Vertices vert;
vert.d = fromLight.d;
vert.x = fromLight.o;
vert.id = 7;
vert.cos = f;
vert.n = Vec(0,1,0).norm();
// this one ?
//vert.couleur = spheres[7].e * f / PDF;
// Or this one ?
vert.couleur = spheres[7].e;
PathLight.push_back(vert);
int sizeEye = generateEyePath(PathEye, rayEye, maxDepth);
int sizeLight = generateLightPath(PathLight, fromLight, maxDepth);
for (int s = 0; s < sizeLight; ++s)
{
for (int t = 1; t < sizeEye; ++t)
{
int depth = t + s - 1;
if ((s == 0 && t == 0) || depth < 0 || depth > maxDepth)
continue;
pixelValue = pixelValue + connectPaths(PathEye, PathLight, s, t);
}
}
}
For the EyePath I intersect the geometry then I compute the illumination according to the distance with the light. The colour is black if the point is in the shadow.
Second question : For the eye path and the direct illumination, is the computation good ? I've seen in many code, people use the pdf even in direct illumination. But I'm only using point light and spheres.
int generateEyePath(std::vector<Vertices>& v, Ray eye, int maxDepth)
{
double t;
int id = 0;
Vertices vert;
int RussianRoulette;
while(v.size() <= maxDepth)
{
if(distribRREye(generatorRREye) < 10)
break;
// Intersect all the geometry
// id is the id of the intersected geometry in an array
intersect(eye, t, id);
const Sphere& obj = spheres[id];
// Intersection point
Vec x = eye.o + eye.d * t;
// normal
Vec n = (x - obj.p).norm();
Vec direction = light.p - x;
// Shadow ray
Ray RaytoLight = Ray(x, direction.norm());
const float distance = direction.length();
// shadow
const bool visibility = intersect(RaytoLight, t, id);
const Sphere &lumiere = spheres[id];
float degree = clamp(n.dot((lumiere.p - x).norm()));
// If the intersected geometry is not a light, then in shadow
if(lumiere.e.x == 0)
{
vert.couleur = Vec();
}
else // else we compute the colour
// obj.c is the brdf, lumiere.e is the emission
vert.couleur = (obj.c).mult(lumiere.e / (distance * distance)) * degree;
vert.x = x;
vert.id = id;
vert.n = n;
vert.d = eye.d.normn();
vert.cos = degree;
v.push_back(vert);
eye = generateRayInHemisphere(x,n);
}
return v.size();
}
For the LightPath, for a given point, I compute it according to the previous one and the values at this point. Like in a common path tracing.\n
Third question: Is the colour computation good ?
int generateLightPath(std::vector<Vertices>& v, Ray fromLight, int maxDepth)
{
double t;
int id = 0;
Vertices vert;
Vec previous;
while(v.size() <= maxDepth)
{
if(distribRRLight(generatorRRLight) < 10)
break;
previous = v.back().couleur;
intersect(fromLight, t, id);
// intersected geometry
const Sphere& obj = spheres[id];
// Intersection point
Vec x = fromLight.o + fromLight.d * t;
// normal
Vec n = (x - obj.p).norm();
double f = clamp(n.dot(fromLight.d.norm()));
// obj.c is the brdf
vert.couleur = previous.mult(((obj.c / M_PI) * f) / PDF);
vert.x = x;
vert.id = id;
vert.n = n;
vert.d = fromLight.d.norm();
vert.cos = f;
v.push_back(vert);
fromLight = generateRayInHemisphere(x,n);
}
return v.size();
}
For the moment I get this result.
enter image description here
The connecting function will come once EyePath and LightPath are good.
Thank you all
Try the spherical reference scene mentioned in this paper. I think then you can work out most of your questions by yourself since it has an analytical solution.
https://www.researchgate.net/publication/221546261_Testing_Monte-Carlo_Global_Illumination_Methods_with_Analytically_Computable_Scenes
It would save your time to implement and verify your understanding with path tracing and light tracing first, then try to combine them with weights.
The problem: I want balloons rise up in the air. They can collide with one another and they can rotate. With a gravitiy in upright direction that works fine.
But now I want to connect a basket to each balloon. Doing so will result in the basket "flying" up like the balloon. But in real life the basket should be "heavier" than the balloon, so it would always point downwards to earth.
How would I achieve that ?
One way to simulate a balloon with a basket is to set gravity to be in the downward direction to pull the basket toward the bottom of the scene and apply a buoyant force to the balloon to create lift. Here's an example of how to do that:
Define the constants
#define kRandMax 0x7fffffff
#define kNumBalloons 5
Create the balloons and baskets
self.physicsWorld.gravity = CGVectorMake(0, -3);
for (int i=0;i<kNumBalloons;i++) {
// Create a balloon near the bottom of the scene with a random x position
SKShapeNode *balloon = [SKShapeNode shapeNodeWithCircleOfRadius:16];
balloon.fillColor = [SKColor redColor];
balloon.name = #"balloon";
balloon.physicsBody.allowsRotation = NO;
CGFloat rand1 = arc4random_uniform(kRandMax) / (double)kRandMax;
// A value in (-view.frame.size.width/2, view.frame.size.width/2)
CGFloat xOffset = (rand1 - 0.5) * view.frame.size.width / 2;
balloon.position = CGPointMake (CGRectGetMidX(view.frame)+xOffset,
CGRectGetMinY(view.frame)+50);
balloon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:balloon];
// Create the basket
SKSpriteNode *basket = [SKSpriteNode spriteNodeWithColor:[SKColor brownColor] size:CGSizeMake(8, 8)];
basket.position = CGPointMake(balloon.position.x, balloon.position.y-24);
basket.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:basket.size.width/2];
[self addChild:basket];
// Connect the balloon and basket with an SKPhysicsJointPin
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:balloon.physicsBody
bodyB:basket.physicsBody anchor:balloon.position];
[self.physicsWorld addJoint:joint];
}
In the update method, apply a force to each balloon to create lift
-(void)update:(CFTimeInterval)currentTime {
// Apply buoyant force to all balloons in the scene
[self enumerateChildNodesWithName:#"//balloon" usingBlock:^(SKNode *node, BOOL *stop){
// Adjust force as needed
[node.physicsBody applyForce:CGVectorMake(0, 17.2)];
}];
}
Look into SKPhysicsBody documentations (can be found here).
Look into Defining a Body’s Physical Properties section.
Every physics body has properties (such as mass) that you want to play with, in order to get a more 'realistic' behaviour.
If you want a less general answer,
Post some code examples.
Good luck mate.
I have now a solution like this:
I create a forcefield going up
func createForceField(strength:Float) {
// add force field
for i in 1...9 {
let field = SKFieldNode.electricField()
field.physicsBody?.categoryBitMask = PhysikcsCategory.ForceField
field.position = CGPointMake(frame.size.width*0.1*CGFloat(i), -1000)
field.strength = strength
addChild(field)
}
}
Before starting the game I turn on gravity and forcefield
override init(size: CGSize) {
super.init(size: size)
// Set-Up forcefield
createForceField(0.1)
self.physicsWorld.gravity = CGVectorMake(0, -0.09)
startGame()
}
here I create the balloons with a rope and tag, the balloon is charged so it will react to the forcefield
func createBalloon(vokabel:Vokabel) {
let x = self.frame.width*0.2
let y = self.frame.height*0.2
// Balloon
var balloon = BalloonNode()
balloon.size = CGSizeMake(balloon.size.width*0.7, balloon.size.height*0.7)
balloon.position = CGPointMake(x-6,y)
balloon.name = "SPRITE"
balloon.vokabel = vokabel
balloon.score = 50
balloonLayer.addChild(balloon)
// Special Start
var specialStar = SKSpriteNode(imageNamed: "Star")
specialStar.size = CGSizeMake(balloon.size.width*0.7, balloon.size.height*0.7)
specialStar.hidden = true
specialStar.name = "STAR"
balloon.addChild(specialStar)
// Rope
let rope = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 2, height: 15))
rope.position = CGPointMake(x, y-balloon.size.height/2-rope.size.height/2)
rope.alpha = 0.2
balloonLayer.addChild(rope)
// Tag
var labelText = getLabelText(vokabel)
let tag = createTagWithText(labelText)
tag.position = CGPointMake(x, y-balloon.size.height/2-rope.size.height)
balloonLayer.addChild(tag)
self.physicsWorld.gravity=CGVectorMake(0.0, -0.3)
// physical bodies
balloon.physicsBody = SKPhysicsBody(rectangleOfSize: balloon.size)
rope.physicsBody = SKPhysicsBody(rectangleOfSize: rope.size)
tag.physicsBody = SKPhysicsBody(rectangleOfSize: tag.size)
// physical forces
balloon.physicsBody?.allowsRotation = false
balloon.physicsBody?.charge = 0.5
tag.physicsBody?.allowsRotation = false
tag.physicsBody?.linearDamping = 1.0
rope.physicsBody?.linearDamping = 1.0
// anchor Points
let anchorBalloonRope = CGPointMake(x, y-balloon.size.height/2)
let anchorRopeTag = CGPointMake(x, y-balloon.size.height/2-rope.size.height)
// create joints
let joint = SKPhysicsJointPin.jointWithBodyA(balloon.physicsBody, bodyB: rope.physicsBody, anchor: anchorBalloonRope)
let joint_rope_tag = SKPhysicsJointPin.jointWithBodyA(rope.physicsBody, bodyB: tag.physicsBody, anchor: anchorRopeTag)
// add joints to physic
self.physicsWorld.addJoint(joint)
self.physicsWorld.addJoint(joint_rope_tag)
// give it a horizontal push
balloon.physicsBody?.applyImpulse(CGVectorMake(10.0, 0.0))
// BalloonNode ( is just a layer, has no special position so it is the same coordinate system as the gamelayer)
let node = BalloonNode()
node.vokabel = vokabel
node.score = SCORE_STANDARD
node.name = "BALLOON"
node.rope = rope
node.tag = tag
}
I have two points (let's call them pointA and pointB) of type SCNVector3. I want to draw a line between them. Seems like it should be easy, but can't find a way to do it.
I see two options, both have issues:
Use a SCNCylinder with a small radius, with length |pointA-pointB| and then position it/rotate it.
Use a custom SCNGeometry but not sure how; would have to define two triangles to form a very thin rectangle perhaps?
It seems like there should be an easier way of doing this, but I can't seem to find one.
Edit: Using the triangle method gives me this for drawing a line between (0,0,0) and (10,10,10):
CGFloat delta = 0.1;
SCNVector3 positions[] = { SCNVector3Make(0,0,0),
SCNVector3Make(10, 10, 10),
SCNVector3Make(0+delta, 0+delta, 0+delta),
SCNVector3Make(10+delta, 10+delta, 10+delta)};
int indicies[] = {
0,2,1,
1,2,3
};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:4];
NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:2 bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource] elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
But there are problems: due to the normals, you can only see this line from one side! It's invisible from the other side. Also, if "delta" is too small you can't see the line at all. As it is, it's technically a rectangle, rather than the line I was going for, which might result in small graphical glitches if I want to draw multiple joined up lines.
Here's a simple extension in Swift:
extension SCNGeometry {
class func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
let indices: [Int32] = [0, 1]
let source = SCNGeometrySource(vertices: [vector1, vector2])
let element = SCNGeometryElement(indices: indices, primitiveType: .Line)
return SCNGeometry(sources: [source], elements: [element])
}
}
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.
New code for a line from (0, 0, 0) to (10, 10, 10) below.
I'm not sure if it could be improved further.
SCNVector3 positions[] = {
SCNVector3Make(0.0, 0.0, 0.0),
SCNVector3Make(10.0, 10.0, 10.0)
};
int indices[] = {0, 1};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions
count:2];
NSData *indexData = [NSData dataWithBytes:indices
length:sizeof(indices)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
primitiveType:SCNGeometryPrimitiveTypeLine
primitiveCount:1
bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource]
elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Here's one solution
class func lineBetweenNodeA(nodeA: SCNNode, nodeB: SCNNode) -> SCNNode {
let positions: [Float32] = [nodeA.position.x, nodeA.position.y, nodeA.position.z, nodeB.position.x, nodeB.position.y, nodeB.position.z]
let positionData = NSData(bytes: positions, length: MemoryLayout<Float32>.size*positions.count)
let indices: [Int32] = [0, 1]
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
let source = SCNGeometrySource(data: positionData as Data, semantic: SCNGeometrySource.Semantic.vertex, vectorCount: indices.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float32>.size, dataOffset: 0, dataStride: MemoryLayout<Float32>.size * 3)
let element = SCNGeometryElement(data: indexData as Data, primitiveType: SCNGeometryPrimitiveType.line, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)
let line = SCNGeometry(sources: [source], elements: [element])
return SCNNode(geometry: line)
}
if you would like to update the line width or anything related to modifying properties of the drawn line, you'll want to use one of the openGL calls in SceneKit's rendering callback:
func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
//Makes the lines thicker
glLineWidth(20)
}
Here is a swift5 version:
func lineBetweenNodes(positionA: SCNVector3, positionB: SCNVector3, inScene: SCNScene) -> SCNNode {
let vector = SCNVector3(positionA.x - positionB.x, positionA.y - positionB.y, positionA.z - positionB.z)
let distance = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
let midPosition = SCNVector3 (x:(positionA.x + positionB.x) / 2, y:(positionA.y + positionB.y) / 2, z:(positionA.z + positionB.z) / 2)
let lineGeometry = SCNCylinder()
lineGeometry.radius = 0.05
lineGeometry.height = distance
lineGeometry.radialSegmentCount = 5
lineGeometry.firstMaterial!.diffuse.contents = GREEN
let lineNode = SCNNode(geometry: lineGeometry)
lineNode.position = midPosition
lineNode.look (at: positionB, up: inScene.rootNode.worldUp, localFront: lineNode.worldUp)
return lineNode
}
So inside your ViewController.cs define your vector points and call a Draw function, then on the last line there - it's just rotating it to look at point b.
var a = someVector3;
var b = someOtherVector3;
nfloat cLength = (nfloat)Vector3Helper.DistanceBetweenPoints(a, b);
var cyclinderLine = CreateGeometry.DrawCylinderBetweenPoints(a, b, cLength, 0.05f, 10);
ARView.Scene.RootNode.Add(cyclinderLine);
cyclinderLine.Look(b, ARView.Scene.RootNode.WorldUp, cyclinderLine.WorldUp);
Create a static CreateGeomery class and put this static method in there
public static SCNNode DrawCylinderBetweenPoints(SCNVector3 a,SCNVector3 b, nfloat length, nfloat radius, int radialSegments){
SCNNode cylinderNode;
SCNCylinder cylinder = new SCNCylinder();
cylinder.Radius = radius;
cylinder.Height = length;
cylinder.RadialSegmentCount = radialSegments;
cylinderNode = SCNNode.FromGeometry(cylinder);
cylinderNode.Position = Vector3Helper.GetMidpoint(a,b);
return cylinderNode;
}
you may also want these utility methods in a static helper class
public static double DistanceBetweenPoints(SCNVector3 a, SCNVector3 b)
{
SCNVector3 vector = new SCNVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
}
public static SCNVector3 GetMidpoint(SCNVector3 a, SCNVector3 b){
float x = (a.X + b.X) / 2;
float y = (a.Y + b.Y) / 2;
float z = (a.Z + b.Z) / 2;
return new SCNVector3(x, y, z);
}
For all my Xamarin c# homies out there.
Here's a solution using triangles that works independent of the direction of the line.
It's constructed using the cross product to get points perpendicular to the line. So you'll need a small SCNVector3 extension, but it'll probably come in handy in other cases, too.
private func makeRect(startPoint: SCNVector3, endPoint: SCNVector3, width: Float ) -> SCNGeometry {
let dir = (endPoint - startPoint).normalized()
let perp = dir.cross(SCNNode.localUp) * width / 2
let firstPoint = startPoint + perp
let secondPoint = startPoint - perp
let thirdPoint = endPoint + perp
let fourthPoint = endPoint - perp
let points = [firstPoint, secondPoint, thirdPoint, fourthPoint]
let indices: [UInt16] = [
1,0,2,
1,2,3
]
let geoSource = SCNGeometrySource(vertices: points)
let geoElement = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geo = SCNGeometry(sources: [geoSource], elements: [geoElement])
geo.firstMaterial?.diffuse.contents = UIColor.blue.cgColor
return geo
}
SCNVector3 extension:
import Foundation
import SceneKit
extension SCNVector3
{
/**
* Returns the length (magnitude) of the vector described by the SCNVector3
*/
func length() -> Float {
return sqrtf(x*x + y*y + z*z)
}
/**
* Normalizes the vector described by the SCNVector3 to length 1.0 and returns
* the result as a new SCNVector3.
*/
func normalized() -> SCNVector3 {
return self / length()
}
/**
* Calculates the cross product between two SCNVector3.
*/
func cross(_ vector: SCNVector3) -> SCNVector3 {
return SCNVector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
}
}
Swift version
To generate a line in a form of cylinder with a certain position and an orientation, let's implement the SCNGeometry extension with a cylinderLine() class method inside. The toughest part here is a trigonometry (for defining cylinder's direction). Here it is:
import SceneKit
extension SCNGeometry {
class func cylinderLine(from: SCNVector3, to: SCNVector3,
segments: Int = 5) -> SCNNode {
let x1 = from.x; let x2 = to.x
let y1 = from.y; let y2 = to.y
let z1 = from.z; let z2 = to.z
let subExpr01 = Float((x2-x1) * (x2-x1))
let subExpr02 = Float((y2-y1) * (y2-y1))
let subExpr03 = Float((z2-z1) * (z2-z1))
let distance = CGFloat(sqrtf(subExpr01 + subExpr02 + subExpr03))
let cylinder = SCNCylinder(radius: 0.005, height: CGFloat(distance))
cylinder.radialSegmentCount = segments
cylinder.firstMaterial?.diffuse.contents = NSColor.systemYellow
let lineNode = SCNNode(geometry: cylinder)
lineNode.position = SCNVector3((x1+x2)/2, (y1+y2)/2, (z1+z2)/2)
lineNode.eulerAngles = SCNVector3(x: CGFloat.pi / 2,
y: acos((to.z-from.z)/CGFloat(distance)),
z: atan2((to.y-from.y), (to.x-from.x)))
return lineNode
}
}
The rest is easy.
class ViewController: NSViewController {
#IBOutlet var sceneView: SCNView!
let scene = SCNScene()
var startingPoint: SCNVector3!
var endingPoint: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.black
sceneView.allowsCameraControl = true
self.startingPoint = SCNVector3Zero
self.endingPoint = SCNVector3(1,1,1)
self.lineInBetween()
}
func lineInBetween() {
self.addSphereDot(position: startingPoint)
self.addSphereDot(position: endingPoint)
self.addLine(start: startingPoint, end: endingPoint)
}
func addSphereDot(position: SCNVector3) {
let sphere = SCNSphere(radius: 0.03)
sphere.firstMaterial?.diffuse.contents = NSColor.red
let node = SCNNode(geometry: sphere)
node.position = position
scene.rootNode.addChildNode(node)
}
func addLine(start: SCNVector3, end: SCNVector3) {
let lineNode = SCNGeometry.cylinderLine(from: start, to: end)
scene.rootNode.addChildNode(lineNode)
}
}
I am trying to develop an iOS game in Cocos2d + Box2d. I want to use elliptical fixtures in Box2D. I tried using b2Capsule shape, but its not exactly what I want as the collision is not proper. Anyone has done this before?
For specific shapes in Box2D you will have to triangulate you original polygon (in your case an ellipse in which you keep a certain number of vertices).
For this, you can use the poly2tri excellent constrained Delaunay triangulation at http://code.google.com/p/poly2tri/
It is very simple. Here is the way I get my triangles :
- (NSArray*) triangulate:(NSArray*)verticesArray
{
NSMutableArray* outputTriangles = [[[NSMutableArray alloc] init] autorelease];
p2t::CDT* triangulationContainer;
vector<p2t::Triangle*> p2tTriangles;
vector< vector<p2t::Point*> > polylines;
vector<p2t::Point*> polyline;
for (hOzPoint2D *point in verticesArray) {
polyline.push_back(new p2t::Point([point x], [point y]));
}
polylines.push_back(polyline);
triangulationContainer = new p2t::CDT(polyline);
triangulationContainer->Triangulate();
p2tTriangles = triangulationContainer->GetTriangles();
for (int i = 0; i < p2tTriangles.size(); i++) {
p2t::Triangle& t = *p2tTriangles[i];
p2t::Point& a = *t.GetPoint(0);
p2t::Point& b = *t.GetPoint(1);
p2t::Point& c = *t.GetPoint(2);
[outputTriangles addObject:[NSArray arrayWithObjects:
[hOzPoint2D point2DWithDoubleX:a.x doubleY:a.y],
[hOzPoint2D point2DWithDoubleX:b.x doubleY:b.y],
[hOzPoint2D point2DWithDoubleX:c.x doubleY:c.y], nil]];
}
delete triangulationContainer;
for(int i = 0; i < polylines.size(); i++) {
vector<p2t::Point*> poly = polylines[i];
FreeClear(poly);
}
return [outputTriangles copy];
}
hOzPoint2D here is my custom point class, but you can pass any couple of coordinates. You don't even have to output a NSArray : you can insert this method in your body creation one.
Be careful that poly2tri has some restrictions :
you can't have twice the same point in your polygon ;
the polygon must not be self-intersecting ;
...
Read the poly2tri page to know more.
The resulting array contains triangles that you attach as fixtures to the same body.
I have used approximation as well. This has some performance drawbacks, but nothing major I guess. Code (Flash ActionScript 3, but you should be able to port that easily):
var vertices:Vector.<b2Vec2> = new Vector.<b2Vec2>();
var a:Number = _image.width / 2 / PhysicsVals.RATIO;
var b:Number = _image.height / 2 / PhysicsVals.RATIO;
var segments:int = ellipse_approximation_vertices_count; (the more the more precise shape is, but the more time it takes to do collision detection)
var segment:Number = 2 * Math.PI / segments;
for (var i:int = 0; i < segments; i++)
{
vertices.push(new b2Vec2(a * Math.cos(segment * i), b * Math.sin(segment * i)));
}
var shape:b2PolygonShape = new b2PolygonShape();
shape.SetAsVector(vertices, vertices.length);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape = shape;