根據iphone 3D Programming所附的例子,重新根據新版的Xcode(4.6.2)改成iPad的例子。這個利字可以開啓兩個OBJ檔,然後顯示所附的3D Model的模樣。以下是實作經過。
1. 開啓一個專案
2. 將c math及GLSL檔從舊的檔案中載入,以下是檔案列表
3. 將Models的檔案從原始例子中載入過來
因為程式所能分析的只能是v及f這兩種語法,因此所附的OBJ檔當然也是相對簡單的。如果真正從網路上去下載一個完整的OBJ檔進來,則會因為Parse Error而讓程式跳出。credits.txt則是原例子中說明下載的路徑,註冊後就可以下載來玩玩。網路上有很多OBJ格式的檔案,當然是完整的語法,所以無法在此例子中使用。
4. ApplicationEngine.ObjViewer.cpp
此例子是從原來ApplicationEngine.ParametricViewer.cpp修改,差別不大,主要是為了區隔,而改變檔名的。
#include "Interfaces.hpp"
#include "ObjSurface.hpp"
#include "ParametricEquations.hpp"
using namespace std;
namespace ObjViewer {
static const int SurfaceCount = 6;
static const int ButtonCount = SurfaceCount - 1;
struct Animation {
bool Active;
float Elapsed;
float Duration;
Visual StartingVisuals[SurfaceCount];
Visual EndingVisuals[SurfaceCount];
};
class ApplicationEngine : public IApplicationEngine {
public:
ApplicationEngine(IRenderingEngine* renderingEngine,
IResourceManager* resourceManager);
~ApplicationEngine();
void Initialize(int width, int height);
void OnFingerUp(ivec2 location);
void OnFingerDown(ivec2 location);
void OnFingerMove(ivec2 oldLocation, ivec2 newLocation);
void Render() const;
void UpdateAnimation(float dt);
private:
void PopulateVisuals(Visual* visuals) const;
int MapToButton(ivec2 touchpoint) const;
vec3 MapToSphere(ivec2 touchpoint) const;
float m_trackballRadius;
ivec2 m_screenSize;
ivec2 m_centerPoint;
ivec2 m_fingerStart;
bool m_spinning;
Quaternion m_orientation;
Quaternion m_previousOrientation;
int m_currentSurface;
ivec2 m_buttonSize;
int m_pressedButton;
int m_buttonSurfaces[ButtonCount];
Animation m_animation;
IRenderingEngine* m_renderingEngine;
IResourceManager* m_resourceManager; // new
};
IApplicationEngine* CreateApplicationEngine(IRenderingEngine* renderingEngine,
IResourceManager* resourceManager)
{
return new ApplicationEngine(renderingEngine,
resourceManager);
}
ApplicationEngine::ApplicationEngine(IRenderingEngine* renderingEngine,
IResourceManager* resourceManager) :
m_spinning(false),
// 此處的用法如同 m_spinning = false
m_pressedButton(-1),
m_renderingEngine(renderingEngine),
// m_renderingEngine = renderingEngine ,這時只是一個空的Pointer
m_resourceManager(resourceManager) // m_resourceManager = resourceManager 空的Pointer
{
m_animation.Active = false;
m_currentSurface = 5; //與前一個例子的稍稍不一樣的排列
m_buttonSurfaces[0] = 1;
m_buttonSurfaces[1] = 2;
m_buttonSurfaces[2] = 3;
m_buttonSurfaces[3] = 4;
m_buttonSurfaces[4] = 0;
}
ApplicationEngine::~ApplicationEngine()
{
delete m_renderingEngine;
}
void ApplicationEngine::Initialize(int width, int height)
{
m_trackballRadius = width / 3;
m_buttonSize.y = height / 10;
m_buttonSize.x = 4 * m_buttonSize.y / 3;
m_screenSize = ivec2(width, height - m_buttonSize.y);
m_centerPoint = m_screenSize / 2;
vector<ISurface*> surfaces(SurfaceCount);
string path = m_resourceManager->GetResourcePath(); //主元件的路徑
surfaces[0] = new ObjSurface(path + "/micronapalmv2.obj");
//surfaces[1] = new ObjSurface(path + "/Ninja.obj");
surfaces[1] = new ObjSurface(path + "/Trex.OBJ");
surfaces[2] = new Torus(1.4, 0.3);
surfaces[3] = new TrefoilKnot(1.8f);
surfaces[4] = new KleinBottle(0.2f);
surfaces[5] = new MobiusStrip(1);
m_renderingEngine->Initialize(surfaces);
for (int i = 0; i < SurfaceCount; i++)
delete surfaces[i];
// 處理完後,資料已經放進renderingEngine中,因此就先釋放掉。
}
void ApplicationEngine::PopulateVisuals(Visual* visuals) const
{
for (int buttonIndex = 0; buttonIndex < ButtonCount; buttonIndex++) {
int visualIndex = m_buttonSurfaces[buttonIndex];
visuals[visualIndex].Color = vec3(0.25f, 0.25f, 0.25f);
if (m_pressedButton == buttonIndex)
visuals[visualIndex].Color = vec3(0.5f, 0.5f, 0.5f);
visuals[visualIndex].ViewportSize = m_buttonSize;
visuals[visualIndex].LowerLeft.x = buttonIndex * m_buttonSize.x;
visuals[visualIndex].LowerLeft.y = 0;
visuals[visualIndex].Orientation = Quaternion();
}
visuals[m_currentSurface].Color = m_spinning ? vec3(1, 1, 0.5f) : vec3(0.25, 0.75, 1);
visuals[m_currentSurface].LowerLeft = ivec2(0, m_buttonSize.y);
visuals[m_currentSurface].ViewportSize = ivec2(m_screenSize.x, m_screenSize.y);
visuals[m_currentSurface].Orientation = m_orientation;
}
void ApplicationEngine::Render() const
{
vector<Visual> visuals(SurfaceCount);
if (!m_animation.Active) {
PopulateVisuals(&visuals[0]);
} else {
float t = m_animation.Elapsed / m_animation.Duration;
for (int i = 0; i < SurfaceCount; i++) {
const Visual& start = m_animation.StartingVisuals[i];
const Visual& end = m_animation.EndingVisuals[i];
Visual& tweened = visuals[i];
tweened.Color = start.Color.Lerp(t, end.Color);
tweened.LowerLeft = start.LowerLeft.Lerp(t, end.LowerLeft);
tweened.ViewportSize = start.ViewportSize.Lerp(t, end.ViewportSize);
tweened.Orientation = start.Orientation.Slerp(t, end.Orientation);
}
}
m_renderingEngine->Render(visuals);
}
.....
}
5. GLView.h
#import "Interfaces.hpp"
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@interface GLView : UIView {
@private
IApplicationEngine* m_applicationEngine;
IRenderingEngine* m_renderingEngine;
IResourceManager* m_resourceManager; //new EAGLContext* m_context;
float m_timestamp;
}
- (void) drawView: (CADisplayLink*) displayLink;
@end
6. GLView.mm
#import "GLView.h"
#define GL_RENDERBUFFER 0x8d41
@implementation GLView
+ (Class) layerClass
{
return [CAEAGLLayer class];
}
- (id) initWithFrame: (CGRect) frame
{
if (self = [super initWithFrame:frame])
{
CAEAGLLayer* eaglLayer = (CAEAGLLayer*) self.layer;
eaglLayer.opaque = YES;
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
m_context = [[EAGLContext alloc] initWithAPI:api];
if (!m_context) {
api = kEAGLRenderingAPIOpenGLES1;
m_context = [[EAGLContext alloc] initWithAPI:api];
}
if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
//[self release];
return nil;
}
// m_resourceManager 作為一個界面去傳送資源(obj檔或貼圖檔...)的相關資料
m_resourceManager = Darwin::CreateResourceManager(); if (api == kEAGLRenderingAPIOpenGLES1) {
NSLog(@"Using OpenGL ES 1.1");
m_renderingEngine = SolidES1::CreateRenderingEngine();
} else {
NSLog(@"Using OpenGL ES 2.0");
m_renderingEngine = SolidES2::CreateRenderingEngine();
}
m_applicationEngine = ObjViewer::CreateApplicationEngine(m_renderingEngine,
m_resourceManager);
[m_context
renderbufferStorage:GL_RENDERBUFFER
fromDrawable: eaglLayer];
int width = CGRectGetWidth(frame);
int height = CGRectGetHeight(frame);
m_applicationEngine->Initialize(width, height);
[self drawView: nil];
m_timestamp = CACurrentMediaTime();
CADisplayLink* displayLink;
displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(drawView:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
}
return self;
}
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil) {
float elapsedSeconds = displayLink.timestamp - m_timestamp;
m_timestamp = displayLink.timestamp;
m_applicationEngine->UpdateAnimation(elapsedSeconds);
}
m_applicationEngine->Render();
[m_context presentRenderbuffer:GL_RENDERBUFFER];
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView: self];
m_applicationEngine->OnFingerDown(ivec2(location.x, location.y));
}
- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView: self];
m_applicationEngine->OnFingerUp(ivec2(location.x, location.y));
}
- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
CGPoint previous = [touch previousLocationInView: self];
CGPoint current = [touch locationInView: self];
m_applicationEngine->OnFingerMove(ivec2(previous.x, previous.y),
ivec2(current.x, current.y));
}
@end
7. Interfaces.hpp
#pragma once
#include "Vector.hpp"
#include "Quaternion.hpp"
#include <vector>
#include <string>
using std::vector;
using std::string;
enum VertexFlags {
VertexFlagsNormals = 1 << 0,
VertexFlagsTexCoords = 1 << 1,
};
struct IApplicationEngine {
virtual void Initialize(int width, int height) = 0;
virtual void Render() const = 0;
virtual void UpdateAnimation(float timeStep) = 0;
virtual void OnFingerUp(ivec2 location) = 0;
virtual void OnFingerDown(ivec2 location) = 0;
virtual void OnFingerMove(ivec2 oldLocation, ivec2 newLocation) = 0;
virtual ~IApplicationEngine() {}
};
struct ISurface {
virtual int GetVertexCount() const = 0;
virtual int GetLineIndexCount() const = 0;
virtual int GetTriangleIndexCount() const = 0;
virtual void GenerateVertices(vector<float>& vertices,
unsigned char flags = 0) const = 0;
virtual void GenerateLineIndices(vector<unsigned short>& indices) const = 0;
virtual void GenerateTriangleIndices(vector<unsigned short>& indices) const = 0;
virtual ~ISurface() {}
};
struct Visual {
vec3 Color;
ivec2 LowerLeft;
ivec2 ViewportSize;
Quaternion Orientation;
};
struct IRenderingEngine {
virtual void Initialize(const vector<ISurface*>& surfaces) = 0;
virtual void Render(const vector<Visual>& visuals) const = 0;
virtual ~IRenderingEngine() {}
};
struct IResourceManager {
virtual string GetResourcePath() const = 0;
virtual void LoadPngImage(const string& filename) = 0;
virtual void* GetImageData() = 0;
virtual ivec2 GetImageSize() = 0;
virtual void UnloadImage() = 0;
virtual ~IResourceManager() {}
};
//namespace ParametricViewer { IApplicationEngine* CreateApplicationEngine(IRenderingEngine*); }namespace ObjViewer { IApplicationEngine* CreateApplicationEngine(IRenderingEngine*, IResourceManager*); } // ObjViewer有定義兩次namespace,此處作為function的宣告(Prototypes)
namespace Darwin { IResourceManager* CreateResourceManager(); } // ResourceManager所用//namespace WireframeES1 { IRenderingEngine* CreateRenderingEngine(); }
//namespace WireframeES2 { IRenderingEngine* CreateRenderingEngine(); }
namespace SolidES1 { IRenderingEngine* CreateRenderingEngine(); }
namespace SolidES2 { IRenderingEngine* CreateRenderingEngine(); }
8. ObjSurface.hpp 讀取OBJ檔的主程式定義檔,定義了處理的Class
#include "Interfaces.hpp"
class ObjSurface : public ISurface {
public:
ObjSurface(const string& name);
int GetVertexCount() const;
int GetLineIndexCount() const { return 0; }
int GetTriangleIndexCount() const;
void GenerateVertices(vector<float>& vertices, unsigned char flags) const;
void GenerateLineIndices(vector<unsigned short>& indices) const {}
void GenerateTriangleIndices(vector<unsigned short>& indices) const;
private:
string m_name; // OBJ檔名
vector<ivec3> m_faces; // 紀錄STL的Array
mutable size_t m_faceCount; // face (f)的數量
mutable size_t m_vertexCount; // vertex (v) 的數量
static const int MaxLineSize = 128; // 一行讀取的最大字數
};
9. ObjSurface.hpp 讀取OBJ檔的主程式檔
#include "ObjSurface.hpp"
#import <list>
#import <fstream>
#import <assert.h>
using namespace std;
ObjSurface::ObjSurface(const string& name) :
m_name(name),
m_faceCount(0),
m_vertexCount(0)
{
m_faces.resize(GetTriangleIndexCount() / 3); // 設定m_faces的記憶體,三角形的面
ifstream objFile(m_name.c_str());
vector<ivec3>::iterator face = m_faces.begin();
while (objFile) {
char c = objFile.get();
if (c == 'f') { //將f 面(Face)的資料一行行分析出來
assert(face != m_faces.end() && "parse error");
objFile >> face->x >> face->y >> face->z;
// 將值射定到m_faces的陣列(vector)中,資料為vertex所對應點的代號/index
*face++ -= ivec3(1, 1, 1); // m_faces記錄三角形面的三個點之index,因為起始點為0開始
}
objFile.ignore(MaxLineSize, '\n');
}
assert(face == m_faces.end() && "parse error");
}
int ObjSurface::GetVertexCount() const
{
if (m_vertexCount != 0)
return m_vertexCount;
ifstream objFile(m_name.c_str());
while (objFile) {
char c = objFile.get();
if (c == 'v')
m_vertexCount++;
objFile.ignore(MaxLineSize, '\n');
}
return m_vertexCount;
}
int ObjSurface::GetTriangleIndexCount() const
{
if (m_faceCount != 0)
return m_faceCount * 3; // 三角形成一個面
ifstream objFile(m_name.c_str());
while (objFile) {
char c = objFile.get();
if (c == 'f') // f 面(Face)的資料
m_faceCount++; // 記錄資料行數(長度)
// 而如果期间遇到"\n"(換行),则停止向前,定位在该处 ,在此處前面所讀都不處理
objFile.ignore(MaxLineSize, '\n');
}
return m_faceCount * 3; //三角形的面,故乘三
}
void ObjSurface::GenerateVertices(vector<float>& floats, unsigned char flags) const
{
// float 是Vertices 傳入的指標
// 確認VertexFlagsNormals為1 這樣後續的處理才會進行
assert(flags == VertexFlagsNormals && "Unsupported flags.");
struct Vertex {
vec3 Position; // 放v的資料 v 幾何體頂點(Geometric vertices)
vec3 Normal; // 放該點的對外法向量
};
// Read in the vertex positions and initialize lighting normals to (0, 0, 0).
floats.resize(GetVertexCount() * 6); //此處為兩個三角形的六個點,查iphone 3D Programming Fig4-3
ifstream objFile(m_name.c_str());
Vertex* vertex = (Vertex*) &floats[0];
while (objFile) {
char c = objFile.get();
if (c == 'v') { // 格式為 V float1 float2 float3 \n
vertex->Normal = vec3(0, 0, 0); //default 法線為0
vec3& position = (vertex++)->Position;
objFile >> position.x >> position.y >> position.z;
}
objFile.ignore(MaxLineSize, '\n'); // 跳過128的char或是遇到分行符號就停止。
}
vertex = (Vertex*) &floats[0];
for (size_t faceIndex = 0; faceIndex < m_faces.size(); ++faceIndex) {
ivec3 face = m_faces[faceIndex];
// Compute the facet normal.
vec3 a = vertex[face.x].Position; // 利用m_faces找出這個面上各點的真實坐標
vec3 b = vertex[face.y].Position;
vec3 c = vertex[face.z].Position;
vec3 facetNormal = (b - a).Cross(c - a); // 利用三個座標,找出法線的向量
// Add the facet normal to the lighting normal of each adjoining vertex.
vertex[face.x].Normal += facetNormal; // 該點所對應的法線相加後,就成為該點真正所對應的對外的法向量。
vertex[face.y].Normal += facetNormal;
vertex[face.z].Normal += facetNormal;
}
// Normalize the normals.
for (int v = 0; v < GetVertexCount(); ++v)
vertex[v].Normal.Normalize();
}
void ObjSurface::GenerateTriangleIndices(vector<unsigned short>& indices) const
{
indices.resize(GetTriangleIndexCount());
vector<unsigned short>::iterator index = indices.begin();
for (vector<ivec3>::const_iterator f = m_faces.begin(); f != m_faces.end(); ++f) {
*index++ = f->x; // 三角形的三個點所對應的index,因為每一個face面為三角形
*index++ = f->y;
*index++ = f->z;
}
}
10. ResourceManager.mm,作為資源管理,但本次只用到讀取檔案的路徑處理
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <string>
#import <iostream>
#import "Interfaces.hpp"
using namespace std;
namespace Darwin {
class ResourceManager : public IResourceManager {
public:
string GetResourcePath() const
{
NSString* bundlePath =[[NSBundle mainBundle] resourcePath];
// 找到主原件 mainBundle的位置
//simulator的位置 /Users/username/Library/Application Support/iPhone Simulator/User/Applications/uuid/ModelViewer.app
// device的位置 /var/mobile/Applications/uuid/ModelViewer.app
return [bundlePath UTF8String]; // 轉成C string object 給 C++ STL string
}
void LoadPngImage(const string& name) // 本次Objviewer用不到
{
NSString* basePath = [[NSString alloc] initWithUTF8String:name.c_str()];
NSBundle* mainBundle = [NSBundle mainBundle];
NSString* fullPath = [mainBundle pathForResource:basePath ofType:@"png"];
UIImage* uiImage = [[UIImage alloc] initWithContentsOfFile:fullPath];
CGImageRef cgImage = uiImage.CGImage;
m_imageSize.x = CGImageGetWidth(cgImage);
m_imageSize.y = CGImageGetHeight(cgImage);
m_imageData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
//[uiImage release];
//[basePath release];
}
void* GetImageData()
{
return (void*) CFDataGetBytePtr(m_imageData);
}
ivec2 GetImageSize()
{
return m_imageSize;
}
void UnloadImage()
{
CFRelease(m_imageData);
}
private:
CFDataRef m_imageData;
ivec2 m_imageSize;
};
IResourceManager* CreateResourceManager()
{
return new ResourceManager();
}
}
11. 結果顯示,與原始iphone版本相同,此例作為瞭解3D檔案讀取的控制方式