I am studying this wavefront obj loader example: https://github.com/jlamarche/iOS-OpenGLES-Stuff/tree/master/Wavefront%20OBJ%20Loader
And in order to improve object loading speed - I decided to encode loaded objects as NSValue and then store in Core data. Later, when corresponding object needs to be shown again - I fetch from database necessary nsvalue and unarchieve it.
It's all working, but problem is - it's not as fast I hoped it would be.
One of the reasons - because, in that example are used struct objects.
I was not successful to encode/decode them, so I used NSMutableArray, in which I write all struct data, and later - I iterate through it to put back values to struct object.
For example, there is struct:
typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D;
It is then defined:
typedef Vertex3D Vector3D;
But in OpenGLWaveFrontObject class it is used like this:
Vector3D *vertexNormals;
how can I encode/decode struct like this?
I've tried like this:
[coder encodeObject:[NSValue value:&vertexNormals withObjCType:#encode(Vector3D)] forKey:#"vertexNormals"];
or like this:
[coder encodeObject:[NSValue value:&vertexNormals withObjCType:#encode(Vector3D[30])] forKey:#"vertexNormals"];
but its not working - if successfully encoded, then when decoding - values are incorrect.
example, how I put back in array of struct necessary values:
vertices = malloc(sizeof(Vertex3D) * [verticesArray count]);
for(int i = 0; i < [verticesArray count]; i++)
{
vertices[i].x = [[[verticesArray objectAtIndex:i] objectAtIndex:0] floatValue];
vertices[i].y = [[[verticesArray objectAtIndex:i] objectAtIndex:1] floatValue];
vertices[i].z = [[[verticesArray objectAtIndex:i] objectAtIndex:2] floatValue];
}
this works, but I have few more struct arrays and in case arrays are big - this becomes a big issue.
EDIT:
Using Amin Negm-Awad provided answer:
If I simply change NSArchiver to NSKeyedArchiver - it throws error:
[NSKeyedArchiver encodeArrayOfObjCType:count:at:]: unsupported type "{?=fff}]" for array encoding'
When I try:
NSValue *encapsulated = [NSValue valueWithBytes:vertexNormals objCType:#encode(Vertex3D[3])];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:encapsulated];
[coder encodeObject:data forKey:#"vertexNormals"];
And vertexNormals were created:
vertexNormals = calloc(3, sizeof(Vector3D));
and filled with similar data You provided.
EDIT 2:
Using updated answer which Amin Negm-Awad provided,
I was successfully able to store struct object as NSData and encode it, and after that - decode it. It works!
example, so that it might help some one else too:
//Encode
NSData *verticesData = [NSData dataWithBytes:vertices length:numberOfVertices * sizeof(Vector3D)];
[coder encodeObject:verticesData forKey:#"vertices"];
//Decode
vertices = malloc(sizeof(Vertex3D) * numberOfVertices);
NSData *verticesData = [decoder decodeObjectForKey:#"vertices"];
[verticesData getBytes:vertices length:sizeof(Vertex3D) * numberOfVertices];
where Vertex3D was a struct:
typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D;
and used as a struct array (I don't know how it is really called):
Vertex3D *vertices;
Unfortunately I was not able to store texture data. I can encode and decode, but decoded data are always randomly bizarre.
I am declaring it in this way:
GLfloat *textureCoords;
And I Encoded/Decoded in this way:
//Encode
NSData *textureCoordsData = [NSData dataWithBytes:textureCoords length:valuesPerCoord * numberOfVertices];
[coder encodeObject:textureCoordsData forKey:#"textureCoords"];
//Decode
textureCoords = malloc(sizeof(GLfloat) * valuesPerCoord * numberOfVertices);
NSData *textureCoordsData = [decoder decodeObjectForKey:#"textureCoords"];
[textureCoordsData getBytes:textureCoords length:valuesPerCoord * numberOfVertices];
What could be the problem?
First of all: If the array has variable length, it is simply not possible to use NSValue. This is documented:
The type you specify must be of constant length. You cannot store C
strings, variable-length arrays and structures, and other data types
of indeterminate length in an NSValue—you should use NSString or
NSData objects for these types.
So you should really take into account to use NSData instead.
But, if you want to use NSValue (because you have a constant-length array):
Both of your approaches has an extra indirection. The first one has the wrong type, too. What should work:
// Create some data
Vertex3D *cArray = malloc(30 * sizeof(Vertex3D));
NSInteger i;
for (i=0; i<30; i++)
{
cArray[i].x = i;
cArray[i].y = 9811;
cArray[i].z = 29-i;
}
NSLog(#"%p", cArray);
// Encapsulate that in a value
// Have a look at the parameters
NSValue *encapsulated = [NSValue valueWithBytes:cArray objCType:#encode(Vertex3D[30])];
// Put it through a coder and store the coded data on disk
NSData *data = [NSArchiver archivedDataWithRootObject:encapsulated];
[data writeToFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath] atomically:YES];
// Out is done here
// from disk
data = [NSData dataWithContentsOfFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath]];
encapsulated = [NSUnarchiver unarchiveObjectWithData:data];
Vertex3D *cArray2 = malloc(30 * sizeof(Vertex3D));
[encapsulated getValue:cArray2];
for (i=0; i<30; i++)
{
NSLog(#"%f %f %f", cArray[i].x, cArray[i].y, cArray[i].z);
}
NSLog(#"%p", cArray2);
This works in my test code
Update because of the problem with keyed achieving:
To store the c array in an instance of NSData instead of an instance of NSValue do this instead
After
NSLog(#"%p", cArray);
change the code to
// Convert to an NSData instance
NSData *data = [NSData dataWithBytes:cArray length:lengthInBytes];
[data writeToFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath] atomically:YES];
// Out is done here
// from disk
data = [NSData dataWithContentsOfFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath]];
Vertex3D *cArray2 = malloc(lengthInBytes);
[data getBytes:cArray2 length:lengthInBytes];
until
for (i=0; i<30; i++)
Related
I have an OpenGL ES based app with the following structure :
I parse vertices from a DAE file which contains a number of objects (using NSXMLParser).
As I parse each set of vertices, I create an object (sceneObject) and set the vertices array in this object to match the parsed vertices. I am then planning to add these sceneObjects to array of objects to be rendered by OpenGL ES.
Initially I had taken an approach of using NSArrays to store the vertice data, however I understand that the OpenGL commands (such as glvertexpointer) do not accept NSObject values, bue instead need raw values (glfloats and gluints etc).
Rather than convert the NSObjects back to GL raw values within the object class, I am now trying to take a struct approach. This is the code where I parse the vertice information :
if (floatArray) {
if (currentlyParsing == kVerticeInformation) {
if (currentParserTagType == kGeometry) {
//NSLog(#"Loaded Vertices %#", string);
NSArray * nums = [string componentsSeparatedByString:#" "];
culmunativeCount += [nums count];
//NSLog(#"Culm Count is %d", culmunativeCount);
[fullParseString appendString : string];
if (checkSumCount <= culmunativeCount) {
//NSLog(#"Full Parse String is %#", fullParseString);
NSArray * finishedArray = [fullParseString componentsSeparatedByString:#" "];
//NSLog(#"Finihsed ARray is %d", [finishedArray count]);
[finishedParsingArray addObjectsFromArray:finishedArray];
//NSLog(#" FINISHED PARSING = %d", [finishedParsingArray count]);
NSUInteger baseIndex;
for (baseIndex=0; baseIndex <[finishedParsingArray count]; baseIndex +=3) {
NSString * xPoint = [finishedParsingArray objectAtIndex:baseIndex];
NSDecimalNumber * errorCheckX = [NSDecimalNumber decimalNumberWithString:xPoint];
float x = [errorCheckX floatValue];
NSString * yPoint = [finishedParsingArray objectAtIndex:baseIndex +1];
NSDecimalNumber * errorCheckY = [NSDecimalNumber decimalNumberWithString:yPoint];
float y = [errorCheckY floatValue];
NSString * zPoint = [finishedParsingArray objectAtIndex:baseIndex+2];
NSDecimalNumber * errorCheckZ = [NSDecimalNumber decimalNumberWithString:zPoint];
float z = [errorCheckZ floatValue];
Vertex3D vertexItem = {x,y,z}
//NSLog(#"Vertices stored are %f, %f, %f", x,y,z);
}
currentlyParsing = kNilSetting;
checkSumCount = 0;
[finishedParsingArray removeAllObjects];
//[finishedArray release];
culmunativeCount = 0;
[fullParseString setString:#""];
}
}
}
}
As you can see, I am now creating a Vertex3D C Struct each time with the parsed vertice information.
My questions are :
How would I then create an array of these individual C Vertex3D structs ?
How can I then pass this array of structs to my sceneObject ?
Create Objective C wrapper class for your array. It can hold several arrays (e.g. vertices, normals and tangents). You know the array size beforehand, so you can preallocate the required amount of space:
#interface MYVertexArray : NSObject {
float* _mVerts;
float* _mNormals;
int _mCount;
}
#property(nonatomic, readonly) float* verts;
#property(nonatomic, readonly) float* norms;
/**
Here you're allocating your arrays (don't forget to free them is dealloc).
*/
-(id) initWithSize:(int) size;
#end
and then in cycle, you're just doing this:
MYVertexArray* my_vertex_data = [[MYVertexArray alloc] initWithSize: [finishedParsingArray count]];
for(int i=0; i<[finishedParsingArray count]; ++i) {
my_vertex_data.verts[i] = [[finishedParsingArray objectAtIndex: i] floatValue];
}
I'm trying to save all the variables in my class into NSUserDefaults using objc/runtime. And below is the code I'm using.
NSUInteger count;
Ivar *iVars = class_copyIvarList([self class], &count);
for (NSUInteger i=0; i<count; i++)
{
Ivar var = iVars[i];
NSString *varName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
NSString *varType = [NSString stringWithCString:ivar_getTypeEncoding(var) encoding:NSUTF8StringEncoding];
if([varType hasPrefix:#"["])
{
NSLog(#"Array");
id var1 = [_manager valueForKey:varName];
NSLog(#"--- %#", var1);
NSData *data = [NSData dataWithBytes:&([_manager valueForKey:varName]) length:sizeof([_manager valueForKey:varName])]
[[NSUserDefaults standardUserDefaults] setObject:[_manager valueForKey:varName] forKey:varName];
}
else
{
NSLog(#"NonArray");
NSLog(#"--- %#", [_manager valueForKey:varName]);
[[NSUserDefaults standardUserDefaults] setObject:[_manager valueForKey:varName] forKey:varName];
}
}
free(iVars);
The problem is that, when there are only primitive datatypes, the above code works just fine. But, when I try to access a array variable like int[], or float[], it gets crashed with SIGABRT. it is not showing any other messages.
valueForKey doesn't return any values for C arrays.
If anybody know how to load values for C-arrays in runtime, please help.
Thanks in advance,
Suran
Unless you always provide a paired length method, your program will never know the length of the array returned. So... you will need to do some work someplace to accomplish this without a crash.
If I really wanted to do what you're doing, I would make the class itself create the array, providing NSData. If this is common, you may want to use a convention:
- (int*)pixelBuffer;
- (NSData *)pixelBufferForSerialization; // << returns a deep copy of
// self.pixelBuffer as an
// NSData instance.
So your above implementation would see that the property defines a scalar array, and then request NSData * data = obj.pixelBufferForSerialization; instead of trying to produce the data itself.
Update
It's best to let the class do it. Here's how to create NSData using such an array:
#interface DataManager : NSObject
{
#private
int* things;
size_t nThings;
}
- (int*)things;
- (NSData *)thingsAsNSData;
#end
#implementation DataManager
- (int*)things
{
return things;
}
- (NSData *)thingsAsNSData
{
// note: you may need to choose an endianness for serialization
if (0 == nThings) return [NSData data];w
return [NSData dataWithBytes:things length:nThings * sizeof(things[0])];
}
#end
Again - you want the class to create the data because it knows its own structure best.
I have the below code in my app which mallocs memory to a struct object. This method is called several times in the code, each time mallocing memory for a new Vector3D object.
What I want to do is capture the pointer to the malloced ShapeClass object (a C struct) so that I can later free each object in my dealloc method.
Can anyone advise me how I can add the pointer to an array ?
Thank you.
vectorCount = [floatArray.array count] / 3;
ShapeClass *vectorArray = malloc(sizeof(Vector3D) * vectorCount);
for (int i=0; i<vectorCount; i++) {
vectorArray[i].x = [[floatArray.array objectAtIndex:(i*3)] floatValue];
vectorArray[i].y = [[floatArray.array objectAtIndex:(i*3)+1] floatValue];
vectorArray[i].z = [[floatArray.array objectAtIndex:(i*3)+2] floatValue];
}
return vectorArray;
// I WANT TO INSERT SOMETHING HERE TO CAPTURE THE POINTER TO vectorArray - E.G ADD IT TO AN ARRAY ?
}
}
return NULL;
}
Use [NSValue valueWithPointer:] to store it in the container classes.
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
SomeObject *object = [[SomeObject alloc] init];
//Store the pointer to object
[dictionary setValue:[NSValue valueWithPointer:object] forKey:#"object"];
I Have
NSData *object1 and another NSData *object2. How can I compare this objects by what percentage they are similar? For example: Object1 similar to Object2 in - 99%. Thanks.
Get the bytes in both cases and iterate through checking how many of them are equal.
uint8_t* bytes1 = (uint8_t*)[object1 bytes];
uint8_t* bytes2 = (uint8_t*)[object2 bytes];
NSUInteger sameCount = 0;
for (NSUInteger i = 0 ; i < MIN([object1 length], [object2 length]) ; ++i)
{
if (bytes1[i] == bytes2[i])
{
sameCount++;
}
}
double fractionSame = (double) sameCount / (double) MIN([object1 length], [object2 length]);
The above assumes if one data is longer than the other, you don't care about the excess.
It really depends on the logic. If you are, for example, trying to compare images (and their data is stored as NSData) then you need to write image comparison algorithms. If it is some other kind of data, then you need to define that semantics first. If all else fails I think #JeremyP answer should suffice.
There's no such thing for NSData. You'll need to write your own NSSortDescriptor thing, optimized for how you want to compare the contents of one NSData to another.
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSSortDescriptor_Class/Reference/Reference.html
Its y first time trying to use NSData and Gamekit. So was wondering am i packing the data properly?
- (void)sendNetworkPacket:(GKSession *)session packetID:(int)packetID
reliable:(BOOL)howtosend
{
// the packet we'll send is resued
static unsigned char networkPacket[kMaxTankPacketSize];
const unsigned int packetHeaderSize = 2 * sizeof(int); // we have two "ints" for our
header
int *pIntData = (int *)&networkPacket[0];
// header info
pIntData[0] = gamePacketNumber++;
pIntData[1] = packetID;
int theLength = 2 * sizeof(int);
for (int i=0; i<([theHands.player1Hand count]); i++)
{
pIntData[2+i] = [[theHands.player1Hand objectAtIndex:i] intValue];
theLenght += sizeof(int);
}
NSData *packet = [NSData dataWithBytes: networkPacket length: theLength];
[session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId]
withDataMode:GKSendDataReliable error:nil];
}
Will the data I put into NSData *packet be valid?
Many Thanks,
-Code
You create the NSData correctly, and it will contain what you expect. But this is rather more complicated than necessary. The following will do too:
enum { kHeaderLength = 2 };
NSMutableData *packet = [NSMutableData dataWithLength: (kHeaderLength + [theHands.player1Hand count]) * sizeof( int )];
int *pIntData = (int *)[packet mutableBytes];
*pIntData++ = gamePacketNumber++;
*pIntData++ = packetID;
for (id thing in theHands.player1Hand) {
*pIntData++ = [thing intValue];
}
[session sendData: packet toPeers: [NSArray arrayWithObject: gamePeerId] withDataMode: GKSendDataReliable error: NULL];
This will have some advantages:
The packet data will not be copied to the NSData object, it will be created directly in there.
You don’t have to depend on a maximum packet size. You used a constant, which is good since you could change that in one place if bigger packets are required some time. But this is not really needed.
Your version is not thread safe since it depends on the static buffer. This might be fine, since not every method has to be thread-safe. But this is something one has to look out for.
Using fast enumeration also helps keeping the overhead down and is more readable.