SceneKit : Extract data from SCNGeometryElement - ios

I am using SceneKit, and I have an issue:
How can I extract the data from a SCNGeometryElement object ?
I use this method :
- (void)geometryElements:(SCNNode *)node {
for (int indexElement = 0; indexElement < node.geometry.geometryElementCount; indexElement++) {
SCNGeometryElement *currentElement = [node.geometry geometryElementAtIndex:indexElement];
NSLog(#"\n");
NSLog(#"bytes per index : %d", currentElement.bytesPerIndex);
NSLog(#"number element : %d", currentElement.primitiveCount);
NSLog(#"data lenght : %d", currentElement.data.length);
for (int indexPrimitive = 0; indexPrimitive < currentElement.primitiveCount; indexPrimitive++) {
int array[3];
memset(array, 0, 3);
[currentElement.data getBytes:&array range:NSMakeRange(indexPrimitive * 3, (currentElement.bytesPerIndex * 3))];
NSLog(#"currentelement : %d %d %d", array[0], array[1], array[3]);
}
}
The result is not good :
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14539995 -1068223968 -379286778
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14737374 -1068223968 -379286778
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14934753 -1068223968 -379286778
Thanks in advance.

a few notes:
you should rely on geometryElement.primitiveType instead of hard coding 3 (unless you are sure that you're always dealing with SCNGeometryPrimitiveTypeTriangles)
it seems that the range's location does not take geometryElement.bytesPerIndex into account
your buffer is of size 3 * sizeof(int) but should be of size numberOfIndicesPerPrimitive * geometryElement.bytesPerIndex

As mnuages said, you should confirm primtive type and data type of index first.
Your code only work if index type is int.
Here is some code work for me. I only deals that geometry consisted of triangles.
void extractInfoFromGeoElement(NSString* scenePath){
NSURL *url = [NSURL fileURLWithPath:scenePath];
SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:nil];
SCNGeometry *geo = scene.rootNode.childNodes.firstObject.geometry;
SCNGeometryElement *elem = geo.geometryElements.firstObject;
NSInteger componentOfPrimitive = (elem.primitiveType == SCNGeometryPrimitiveTypeTriangles) ? 3 : 0;
if (!componentOfPrimitive) {//TODO: Code deals with triangle primitive only
return;
}
for (int i=0; i<elem.primitiveCount; i++) {
void *idxsPtr = NULL;
int stride = 3*i;
if (elem.bytesPerIndex == 2) {
short *idxsShort = malloc(sizeof(short)*3);
idxsPtr = idxsShort;
}else if (elem.bytesPerIndex == 4){
int *idxsInt = malloc(sizeof(int)*3);
idxsPtr = idxsInt;
}else{
NSLog(#"unknow index type");
return;
}
[elem.data getBytes:idxsPtr range:NSMakeRange(stride*elem.bytesPerIndex, elem.bytesPerIndex*3)];
if (elem.bytesPerIndex == 2) {
NSLog(#"triangle %d : %d, %d, %d\n",i,*(short*)idxsPtr,*((short*)idxsPtr+1),*((short*)idxsPtr+2));
}else{
NSLog(#"triangle %d : %d, %d, %d\n",i,*(int*)idxsPtr,*((int*)idxsPtr+1),*((int*)idxsPtr+2));
}
//Free
free(idxsPtr);
}
}

As the original question has a Swift tag, I am posting my solution in Swift 5.
extension SCNGeometryElement {
/// Gets the `Element` vertices
func getVertices() -> [SCNVector3] {
func vectorFromData<UInt: BinaryInteger>(_ float: UInt.Type, index: Int) -> SCNVector3 {
assert(bytesPerIndex == MemoryLayout<UInt>.size)
let vectorData = UnsafeMutablePointer<UInt>.allocate(capacity: bytesPerIndex)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: primitiveCount)
let stride = 3 * index
self.data.copyBytes(to: buffer, from: stride * bytesPerIndex..<(stride * bytesPerIndex) + 3)
return SCNVector3(
CGFloat.NativeType(vectorData[0]),
CGFloat.NativeType(vectorData[1]),
CGFloat.NativeType(vectorData[2])
)
}
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: self.primitiveCount)
return vectors.indices.map { index -> SCNVector3 in
switch bytesPerIndex {
case 2:
return vectorFromData(Int16.self, index: index)
case 4:
return vectorFromData(Int.self, index: index)
case 8:
return SCNVector3Zero
default:
return SCNVector3Zero
}
}
}
}
There might be a bit of an indentation problem here, but yes, it is a function inside another one. The objective is the same as the other answers. Create a buffer, define what types of numbers the buffer is going to handle, build the SCNVector3 and return the array.

Related

Find numbers in array that appear 2 or more times

I need to find duplicated numbers (that appear 2 or more times) in array how can I do it without using NSCountedSet?
This is a solution I did:
NSCountedSet *countedSet = [NSCountedSet setWithArray:array];
__block NSUInteger totalNumberOfDuplicates = 0;
[countedSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
{
NSUInteger duplicateCountForObject = [countedSet countForObject:obj];
if (duplicateCountForObject > 1)
totalNumberOfDuplicates += duplicateCountForObject;
NSLog(#"%# appears %ld times", obj, duplicateCountForObject);
}];
This is a solution that can be achieved using Swift, but you can use any language to achieve this result:
func checkDuplicatedNumbers()
{
let array = [1, 2, 3, 4, 0, 1, 5, 2, 1, 1, 1, 4]
var dictioanry = [Int: Int]()
for element in array
{
if let value = dictioanry[element] {
let newValue = value + 1
dictioanry[element] = newValue
} else {
dictioanry[element] = 1
}
}
for key in dictioanry.keys {
let count = dictioanry[key]
if (count > 1) {
print("Number \(key) repeats \(count) times")
}
}
}

It is possible to use array reduce concept from swift in objective c?

I have this lines of code in Swift:
let graphPoints:[Int] = [4, 2, 6, 4, 5, 8, 3]
let average = graphPoints.reduce(0, combine: +) / graphPoints.count
It is possible to "translate" this lines of code in objective c code?
It's not very clear for me how reduce combine concept works. I read about it but still is unclear.
I took the code from this tutorial: http://www.raywenderlich.com/90693/modern-core-graphics-with-swift-part-2
Please help. Thanks.
let's say you have some NSNumbers stored in an NSArray you can use this KVC collection operator:
NSArray *someNumbers = #[#0, #1.1, #2, #3.4, #5, #6.7];
NSNumber *average = [someNumbers valueForKeyPath:#"#avg.self"];
For Objective-C, I would add the Higher-Order-Functions to this list of answers: https://github.com/fanpyi/Higher-Order-Functions
#import <Foundation/Foundation.h>
typedef id (^ReduceBlock)(id accumulator,id item);
#interface NSArray (HigherOrderFunctions)
-(id)reduce:(id)initial combine:(ReduceBlock)combine;
#end
#import "NSArray+HigherOrderFunctions.h"
#implementation NSArray (HigherOrderFunctions)
-(id)reduce:(id)initial combine:(ReduceBlock)combine{
id accumulator = initial;
for (id item in self) {
accumulator = combine(accumulator, item);
}
return accumulator;
}
#end
example:
NSArray *numbers = #[#5,#7,#3,#8];
NSNumber *sum = [numbers reduce:#0 combine:^id(id accumulator, id item) {
return #([item intValue] + [accumulator intValue]);
}];
NSNumber *multiplier = [numbers reduce:#1 combine:^id(id accumulator, id item) {
return #([item intValue] * [accumulator intValue]);
}];
NSLog(#"sum=%#,multiplier=%#",sum,multiplier);
The reduce function is not standard in Objective-C. You can implement it as an extension of NSArray though.
In your case, you have an array of Int in Swift. You cannot have that in Objective-C, you need an array of NSNumber.
Here is an implementation of reduce that should work in your case:
#implementation NSArray (Helpers)
- (NSInteger)reduceInt:(NSInteger)initial combine:(NSInteger (^)(NSInteger acum, NSInteger element))block {
if (!self) {
return initial;
}
NSInteger acum = initial;
for (id element in self) {
if ([element isKindOfClass:[NSNumber class]]) {
acum = block(acum, [(NSNumber *)element integerValue]);
}
}
return acum;
}
#end
You can use it then with your array, something like this:
NSArray *a = #[#1, #2, #3];
NSInteger result = [a reduceInt:0 combine:^NSInteger(NSInteger acum, NSInteger element) {
return acum + element;
}];
how to translate reduce to ObjC (or better to say how to solve your "average problem" in Objective C) was perfectly answered by André Slotta. the swift reduce is much more than that. I will try to answer the second part of your question, how the concept works in swift
func reduce<T>(initial: T, #noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
Return the result
of repeatedly calling combine with an accumulated value initialized to
initial and each element of self, in turn, i.e. return
combine(combine(...combine(combine(initial, self[0]),
self[1]),...self[count-2]), self[count-1]).
let arr: Array<Int> = [1,2,3,4,5]
let sum = arr.reduce(0) { (sum, i) -> Int in
return sum + i
}
print(sum) // 15
// this is an quasi equivalent of
var sum1 = 0 // ..... reduce(0)....
arr.forEach { (elementValue) -> Void in
sum1 = sum1 + elementValue // ...{ return sum + i }
}
print(sum1) // 15 reduce function will return accumulated inital value
// reduce is part of SequenceType protocol, that is why
let arr1 = ["H","e","l","l","o"," ","w","o","r","l","d"]
let str = arr1.reduce("") { (str, s) -> String in
str + s
}
// works the same way
print(str) // "Hello world"
// let have a litle bit more complex example, to see how powerful, useful and easy to use reduce can be
let dict = arr1.reduce([:]) { (var dict, s) -> Dictionary<Int,String> in
let i = dict.count
dict.updateValue(s, forKey: i+1)
return dict
}
print(dict) // [11: "d", 10: "l", 2: "e", 4: "l", 9: "r", 5: "o", 6: " ", 7: "w", 3: "l", 1: "H", 8: "o"]
Write an NSArray extension
- (NSInteger)reduceStart:(NSInteger)start combine:(NSInteger(^)(NSInteger x, NSInteger y))combine {
for (NSNumber* n in self) {
if ([n isKindOfClass:[NSNumber class]]) {
start = combine (start, n.integerValue);
}
}
return start;
}
fix all mistakes that I made, and that's it. Just less flexible than Swift.

How to translate push tokens between NSData and NSString representations in Swift?

I have used the following Objective C routines for years, to convert a NSData push token into a NSString (for use by a web side push service), and the inverse, to take a known NSString version of the token and recreate the NSData representation. Now, I find the need for the exact same capabilities, but in Swift.
The dataToHex Objective C code essentially uses printf formatting:
- (NSString *)dataToHex:(NSData *)data
{
NSMutableString *str = [NSMutableString stringWithCapacity:100];
const unsigned char *p = [data bytes];
NSUInteger len = [data length];
for(int i=0; i<len; ++i) {
[str appendFormat:#"%02.2X", p[i]];
}
return str;
}
The inverse translation is:
- (NSData *)hexToData:(NSString *)str
{
const char *ptr = [str cStringUsingEncoding:NSASCIIStringEncoding];
NSUInteger len = [str length]/2;
NSMutableData *data = [NSMutableData dataWithCapacity:len];
while(len--) {
char num[5] = (char[]){ '0', 'x', 0, 0, 0 };
num[2] = *ptr++;
num[3] = *ptr++;
uint8_t n = (uint8_t)strtol(num, NULL, 0);
[data appendBytes:&n length:1];
}
return data;
}
By "cleverly" overwriting a two byes in an ASCII array, the "0xXX" string is converted into a byte, which is then appended to the mutable data object.
Now that I'm coding in Swift, I need the same capabilities but have not found any posts with anything like the code above in Swift.
Converting from the NSData representation as supplied by iOS matches the Objective C code almost line for line:
func dataToHex(data: NSData) -> String
{
var str: String = String()
let p = UnsafePointer<UInt8>(data.bytes)
let len = data.length
for var i=0; i<len; ++i {
str += String(format: "%02.2X", p[i])
}
return str
}
However, given an NSString object, the conversion back to a NSData object is a bit harder. You might need to do this if you are testing in the Simulator, have a string token from a real device, and need it to say register with a service.
The first approach I took tried to replicate code I used before, by creating a string with character pairs, and calling strtol:
func hexToData0(str: NSString) -> NSData {
let len = str.length/2
var data = NSMutableData(capacity:len)!
var num: [Int8] = [ 0, 0, 0 ]
let ptr = str.cStringUsingEncoding(NSUTF8StringEncoding)
for var i=0; i<len; ++i {
num[0] = ptr[i*2+0]
num[1] = ptr[i*2+1]
var n = UInt8 ( strtol(&num, nil, 16) )
data.appendBytes(&n, length:1)
}
return data;
}
I just felt the strtol was a bit of a hack, so I did the same using NSScanner that about the same code size, while most likely less efficient:
func hexToData1(str: NSString) -> NSData {
var data = NSMutableData(capacity: str.length/2)!
for var i = 0; i<str.length; i+=2 {
let r = NSRange(location: i, length: 2)
let s = str.substringWithRange(r)
let sc = NSScanner(string: s)
var val: UInt32 = 0
let ret = sc.scanHexInt(&val)
if ret {
var b = UInt8(val)
data.appendBytes(&b, length: 1)
} else {
assert(false, "Yikes!")
}
}
return data
}
Then, it occurred to me that I could do it all in Swift, no Darwin or Foundation needed, at the expense of a few more lines of code:
// Swift 4
func hexToData(str: String) -> Data {
let len = str.count/2
var data = Data(capacity:len)
let ptr = str.cString(using: String.Encoding.utf8)!
for i in 0..<len {
var num: UInt8 = 0
var multi: UInt8 = 16;
for j in 0..<2 {
let c: UInt8 = UInt8(ptr[i*2+j])
var offset: UInt8 = 0
switch c {
case 48...57: // '0'-'9'
offset = 48
case 65...70: // 'A'-'F'
offset = 65 - 10 // 10 since 'A' is 10, not 0
case 97...102: // 'a'-'f'
offset = 97 - 10 // 10 since 'a' is 10, not 0
default:
assert(false)
}
num += (c - offset)*multi
multi = 1
}
data.append(num)
}
return data;
}
I'm using the final hexToData in my code.

Objective-C - How to get sum of Boleans?

Through the code below, I can get an output such as :
0
1
1
What I want is to output the sum of these booleans values, in my case the result will be : 2 because we have 0+1+1
The code [Update] :
-(void)markers{
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
BOOL test3 = [test containsCoordinate:tg];
{
if (test3 == 1)
{
marker.position = CLLocationCoordinate2DMake(location.latitude, location.longitude);
}else if (test3 == 0)
{
marker.position = CLLocationCoordinate2DMake(0, 0);
}
}
}
}
Rather than sum BOOLs, which is counterintuitive, loop over whatever you are using to get the BOOL values, and if you get YES, increment a counter. This will be the number of YESs that you have.
If you have an array of BOOLs, you could just filter the array with a predicate to get the YES values and the length of the resulting array is the number of YESs that you have.
Edited to add code samples following OP comments
Incrementing a counter
NSUInteger numberOfBools = 0;
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
if ([test containsCoordinate:tg1]) { ++numberOfBools; }
if ([test containsCoordinate:tg2]) { ++numberOfBools: }
... // other tests here;
// numberOfBools now contains the number of passing tests.
Edited Again, after the full code was added
// snipped code above here
// This is where you add the counter and initialise it to 0
NSUInteger numberOfBools = 0;
for (NSDictionary *dictionary in array)
{
// Snip more code to this point
BOOL test3 = [test containsCoordinate:tg];
{
if (test3)
{
// This is where you increment the counter
++numberOfBools;
// Snip more code to the end of the for block
}
// Now numberOfBools shows how many things passed test3
int sum = (test3 ? 1 : 0) + (testX ? 1 : 0) + (testY ? 1 : 0);
And not so weird variant:
#define BOOL_TO_INT(val) ((val) ? 1 : 0)
int sum = BOOL_TO_INT(test3) + BOOL_TO_INT(testX) + BOOL_TO_INT(testY);
You can just add BOOLs since bools are just integers. e.g. :
int sum = 0;
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
BOOL test3 = [test containsCoordinate:tg];
//Obtain bolean values :
BOOL testX = /* other test */;
BOOL testY = /* other test */;
sum = test3 + testX + testY
This is a bad idea however, as BOOLs aren't necessarily 1 or 0. They are 0 and not 0
BOOL is just a typedef-ed char: typedef signed char BOOL; YES and NO are 1 and 0, but BOOL variable = 2 is perfectly valid
For example:
- (int) testX
{
if(inState1) return 1;
if(inState2) return 2;
else return 0;
}
BOOL textXResult = [self testX]; //Might return 2, this is still equivalent to YES.
The best solution is to iterate your BOOLs and instead count the number of YESes.
Another way to do this is to assume that if any one value is false, then the entire array is false, so, loop over the array until a false value is found, then break:
BOOL retval = true; //return holder variable
/*'boolsNumArray' is an NSArray of NSNumber instances, converted from BOOLs:
//BOOL-to-NSNumber conversion (makes 'boolsNumArray' NSArray, below)!
'[myNSArray addObject:[NSNumber numberWithBool:yourNextBOOL]]'
*/
for(NSNumber* nextNum in boolsNumArray) {
if([nextNum boolValue] == false) {
retval = false;
break;
}
}
return retval;

CGRectFromString() for general structs

#"{0, 1.0, 0, 1.0}"
I wish to convert the above string to a struct like this:
struct MyVector4 {
CGFloat one;
CGFloat two;
CGFloat three;
CGFloat four;
};
typedef struct MyVector4 MyVector4;
CGRectFromString() does the same thing, only for CGRect. How can I do it for my own structs?
If there is a function for rect it means that it is not working by default.
You have to create your own function something like MyVector4FromString.
You may like to to know that you can init struct object like this also.
MyVector4 v1 = {1.1, 2.2, 3.3, 4.4};
This is a very easy C syntax. so I don't think you require to init from string.
See here : 4.7 — Structs
But if you are getting string from server or from other function than yes you have to create your function to do this. You can parse string and init 4 float value.
This link will help you to divide string in multiple part : Split a String into an Array
All the best.
It can be done in the following way:
-(MyVector4)myVector4FromString:(NSString*)string
{
NSString *str = nil;
str = [string substringWithRange:NSMakeRange(1, string.length - 1)];
NSArray *strs = [str componentsSeparatedByString:#","];
MyVector4 myVec4 = {0,0,0,0};
for (int i=0; i<4; i++)
{
CGFloat value = ((NSString*)[strs objectAtIndex:i]).floatValue;
if (i==0) { myVec4.one = value; } else
if (i==1) { myVec4.two = value; } else
if (i==2) { myVec4.three = value; } else
if (i==3) { myVec4.four = value; }
}
return myVec4;
}
This function can parse strings in format shown in your question like #"{12.0,24.034,0.98,100}"

Resources