|
IFCMng.h |
#ifndef __IFCMNG_H_SUNMOONBIT_20_TH_ILLJO #define __IFCMNG_H_SUNMOONBIT_20_TH_ILLJO
class IFCMng { /**************************************************************************************** * 싱글톤 ****************************************************************************************/ static IFCMng* singleton;
public : static IFCMng* GetIFCMngInstance(); static bool RemoveIFCMngInstance( const IFCMng* arg );
/**************************************************************************************** * 생성자및소멸자 ****************************************************************************************/
private: IFCMng(void); virtual ~IFCMng(void);
/**************************************************************************************** * Interface ****************************************************************************************/ public : //렌더링에필요한버텍스집합의버텍스수가몇개인지얻어온다. int GetVerticesCount()const;
//렌더링에필요한인덱스집합의인덱스수가몇개인지얻어온다. int GetIndicesCount()const;
//렌더링에필요한버텍스집합을얻어온다. void GetVertices( float pVertices[] );
//렌더링에필요한인덱스집합을얻어온다. void GetIndices( int pIndices[] );
/**************************************************************************************** * Attribute ****************************************************************************************/ private: //Open한IFC파일을나타내는식별자 int ifcModel;
//렌더링에필요한버텍스집합의버텍스수 int noVertices;
//렌더링에필요한인덱스집합의인덱스수 int noIndices;
//렌더링에필요한버텍스집합 float* pVertices;
//렌더링에필요한인덱스집합 int* pIndices; };
#endif |
IFCMng 클래스는 IFC파일을 로드하고 3D렌더링에 필요한 정보들을 추출해 내는 기능만 제공한다. 현재 예광탄 프로젝트에서 사용하기위해 싱글톤으로 구현 했으나 실제로 구현 할 때는 일반적인 개체로 사용되어 져야 한다. 아래는 IFCMng의 구현 부분이다.
IFCMng.cpp |
#include "IFCMng.h"
#include "d3d8.h" //Direct3D를이용 #include "d3dx8.h" //Direct3D를이용
#include "IFCEngine.h" //TNO사의IFC엔진 #include "memory.h"
//사용자정의버텍스포맷 typedef struct CUSTOMVERTEX { float x; float y; float z; float nx; float ny; float nz; } customvertex; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL)
/**************************************************************************************** * 싱글톤 ****************************************************************************************/
IFCMng* IFCMng::singleton = 0;
IFCMng* IFCMng::GetIFCMngInstance() { if( singleton == 0 ) { singleton = new IFCMng(); } return singleton; }
bool IFCMng::RemoveIFCMngInstance( const IFCMng* arg ) { if( singleton != 0 && singleton == arg ) { delete singleton; return true; } return false; }
/**************************************************************************************** * 생성자및소멸자 ****************************************************************************************/
IFCMng::IFCMng(void) { //IFC 파일과스키마파일을오픈. ifcModel = sdaiOpenModelBN(0, "example.ifc", "IFC2X3_TC1.exp");
//현재오픈된IFC파일에서3D로렌더링하기위한 //버텍스, 인덱스집합의사이즈를알려준다. initializeModelling( ifcModel, &noVertices, &noIndices, 1 );
//버텍스, 인덱스집합을받기위한공간을할당한다. pVertices = new float[noVertices * sizeof(CUSTOMVERTEX) / sizeof(float)]; pIndices = new int[noIndices];
//버텍스, 인덱스집합을받는다. finalizeModelling(ifcModel, &pVertices[0], &pIndices[0], D3DFVF_CUSTOMVERTEX); }
IFCMng::~IFCMng(void) { delete pVertices; delete pIndices; sdaiCloseModel(ifcModel); }
/**************************************************************************************** * Interface ****************************************************************************************/
int IFCMng::GetVerticesCount()const { //버텍스집합의원소수를알려준다. //하나의버텍스는총6개의float맴버로구성돼있다. //만약버텍스가3개라면총18개의원소가존재한다. return noVertices * sizeof(CUSTOMVERTEX) / sizeof(float); }
int IFCMng::GetIndicesCount()const { //인덱스집합의원소수를알려준다. return noIndices; }
void IFCMng::GetVertices( float pVertices[] ) { //버텍스집합을넘겨받는다. memcpy( pVertices, this->pVertices, this->noVertices * sizeof(CUSTOMVERTEX) ); }
void IFCMng::GetIndices( int pIndices[] ) { //인덱스집합을넘겨받는다. memcpy( pIndices, this->pIndices, this->noIndices * sizeof(int) ); } |
이제 이 클래스의 인터페이스를 제공하는 DLL을 작성한다.
GetIFC3DInfoDLL.h |
#ifndef __GETIFC3DINFODLL_H_SUNMOONBIT_20_TH_ILLJO #define __GETIFC3DINFODLL_H_SUNMOONBIT_20_TH_ILLJO
#ifdef DLLEXPORT_GETIFC3DINFO_SUNMOONBIT_20_TH_ILLJO #define GETIFC3DINFO_DLL_TYPE __declspec(dllexport) #else #define GETIFC3DINFO_DLL_TYPE __declspec(dllimport) #endif
// //렌더링에필요한버텍스집합의버텍스수가몇개인지얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE int GetVierticesCount();
// //렌더링에필요한인덱스집합의인덱스수가몇개인지얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE int GetIndicesCount();
// //렌더링에필요한버텍스집합을얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE void GetVertices( float viertices[] );
// //렌더링에필요한인덱스집합을얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE void GetIndices( int indices[] ); #endif |
GetIFC3DInfoDLL.cpp |
#define DLLEXPORT_GETIFC3DINFO_SUNMOONBIT_20_TH_ILLJO
#include "GetIFC3DInfoDLL.h" #include "IFCMng.h"
// //렌더링에필요한버텍스집합의버텍스수가몇개인지얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE int GetVierticesCount() { IFCMng* obj = IFCMng::GetIFCMngInstance(); return obj->GetVerticesCount(); }
// //렌더링에필요한인덱스집합의인덱스수가몇개인지얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE int GetIndicesCount() { IFCMng* obj = IFCMng::GetIFCMngInstance(); return obj->GetIndicesCount(); }
// //렌더링에필요한버텍스집합을얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE void GetVertices( float viertices[] ) { IFCMng* obj = IFCMng::GetIFCMngInstance(); obj->GetVertices(viertices); }
// //렌더링에필요한인덱스집합을얻어온다. // extern "C" GETIFC3DINFO_DLL_TYPE void GetIndices( int indices[] ) { IFCMng* obj = IFCMng::GetIFCMngInstance(); obj->GetIndices(indices); } |
2) 랩핑한 엔진을 사용하는 리모팅 서버 구현
GetIFC3DInfoDLL.DLL을 사용하여 리모팅 서비스를 하는 서버를 구현해야 한다. 우선 GetIFC3DInfoDLL을 마이그레이션 하는 클래스를 만들고
IFCMng.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices;
namespace IFCLib { class IFCMng { [DllImport("GetIFC3DInfoDLL.dll")] public static extern int GetVierticesCount();
[DllImport("GetIFC3DInfoDLL.dll")] public static extern int GetIndicesCount();
[DllImport("GetIFC3DInfoDLL.dll")] public static extern void GetVertices(float[] viertices);
[DllImport("GetIFC3DInfoDLL.dll")] public static extern void GetIndices(int[] indices); } } |
IFCMng 클래스를 이용하여 원격 서비스를 하는 원격개체 클래스를 만든다.
IFCRmtObject.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace IFCLib { public class IFCRmtObject :MarshalByRefObject { public float[] GetIFCVertices() { // // 버텍스 집합을 얻어와서 반환한다. //
int verticesSize = IFCMng.GetVierticesCount(); float[] vertices = new float[verticesSize];
IFCMng.GetVertices(vertices);
// // 현재 예광탄 예제에서는 법선정보는 사용하지 않으므로 제거한다. // int compactVerticesSize = verticesSize / 2; float[] compactVertices = new float[compactVerticesSize];
int j = 0; for (int i = 0; i < verticesSize; i += 6) { compactVertices[j] = vertices[i]; compactVertices[j + 1] = vertices[i + 1]; compactVertices[j + 2] = vertices[i + 2]; j += 3; }
return compactVertices; }
public int[] GetIFCIndices() { // //인덱스 집합을 얻어와서 반환한다. //
int indesiceSize = IFCMng.GetIndicesCount(); int[] indices = new int[indesiceSize];
IFCMng.GetIndices(indices);
return indices; } } } |
이제 서버 프로그램에서 다음과 같이 리모팅 서비스를 시작하면 된다.
Program.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting;
namespace IFCEngineRmtService { class Program { static void Main(string[] args) { HttpChannel hc = new HttpChannel(10200); ChannelServices.RegisterChannel(hc);
RemotingConfiguration.RegisterWellKnownServiceType( typeof(IFCLib.IFCRmtObject),"IFCLib.soap",WellKnownObjectMode.Singleton);
Console.ReadLine(); } } } |
3) 리모팅 서비스를 이용하는 웹서비스 구현
안드로이드에서는 직접 C#의 리모팅 서비스를 이용 할 수 없으므로 이에대한 접근 방법을 제공해 주어야 하는데 웹서비스를 이용해서 이 문제를 해결 하였다.
http://192.168.34.74/IFCTest1/Service.asmx |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Services;
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] // ASP.NET AJAX를 사용하여 스크립트에서 이 웹 서비스를 호출하려면 다음 줄의 주석 처리를 제거합니다. // [System.Web.Script.Services.ScriptService] public class Service : System.Web.Services.WebService { public Service () {
//디자인된 구성 요소를 사용하는 경우 다음 줄의 주석 처리를 제거합니다. //InitializeComponent(); }
[WebMethod] public string GetIFCVertices() { // // 원격서비스를 이용해서 버텍스 집합을 얻어 온 후 // 집합의 원소들을 모두 문자로 변환해서 반환한다. // 원소들을 구분하는 구분자로 공백문자를 사용하고 있다. //
IFCLib.IFCRmtObject il = Activator.GetObject(typeof(IFCLib.IFCRmtObject), "http://192.168.34.74:10200/IFCLib.soap") as IFCLib.IFCRmtObject;
float[] vertices = il.GetIFCVertices();
string buf = vertices.Length.ToString() + " ";
foreach (float f in vertices) { buf += f.ToString() + " "; }
return buf; }
[WebMethod] public string GetIFCIndices() { // // 원격서비스를 이용해서 인덱스 집합을 얻어 온 후 // 집합의 원소들을 모두 문자로 변환해서 반환한다. // 원소들을 구분하는 구분자로 공백문자를 사용하고 있다. //
IFCLib.IFCRmtObject il = Activator.GetObject(typeof(IFCLib.IFCRmtObject), "http://192.168.34.74:10200/IFCLib.soap") as IFCLib.IFCRmtObject;
int[] indices = il.GetIFCIndices();
string buf = indices.Length.ToString() + " ";
foreach( int i in indices) { buf += i.ToString() + " "; }
return buf; } } |
이제 위의 웹서비스를 게시하면 서버 쪽은 완료된다.
4) 안드로이드 어플리케이션
다음은 안드로이드 쪽에서 버텍스, 인덱스 집합을 얻어와 파싱하고 뷰잉 하는 어플을 작성 한 예이다.
Run.java |
import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle;
public class Run extends Activity { private GLSurfaceView glSurface;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
//OpenGL ES를 이용해 렌더링한 결과를 보여줄 서페이스뷰를 생성한다. glSurface = new GLSurfaceView(this);
//그리고 렌더링 작업을 처리하는 렌더러를 생성한다. glSurface.setRenderer(new BuildingRenderer());
//만든 서페이스 뷰를 액티비티에 연결한다. setContentView(glSurface); }
@Override protected void onResume() { super.onResume(); glSurface.onResume(); }
@Override protected void onPause() { super.onPause(); glSurface.onPause(); } } |
먼저 액티비티를 작성하는데 OpenGL ES로 렌더링 한 결과를 보여주기 위한 전용 뷰인 GLSurfaceView를 생성하고 액티비티에 연결하는 부분이다. 현재 액티비티가 포커스를 얻고 잃었을 때 GLSurfaceView에서 해야 할 작업들이 있으므로 onResume, onPause등의 이벤트가 발생 했을 때 GLSurfaceView의 onResume, onPause 메소드를 호출해 주어야 한다.
다음은 서페이스뷰에 세팅했던 랜더러 개체 클래스인데 매 프레임마다 Z축으로 오브젝트를 회전시키고 있다.
BuildingRenderer.java |
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLU; import android.opengl.GLSurfaceView.Renderer;
public class BuildingRenderer implements Renderer {
private MyBuildingModel buildingModel;
private float rbri;
public BuildingRenderer() { buildingModel = new MyBuildingModel(); }
// // 서페이스를 생성할 때 호출된다. // public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glShadeModel(GL10.GL_SMOOTH); gl.glClearColor(1.0f, 1.0f, 0.0f, 0.5f); gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); }
// // 프레임마다 렌더링 한다. // public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -3.0f); gl.glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); gl.glRotatef(rbri, 0.0f, 0.0f, 1.0f);
buildingModel.draw(gl);
//매 프레임 마다 z 축으로 rbri 만큼 회전한다. rbri += 2.5f; }
// // 서페이스 크기가 변경될 때 호출된다. // public void onSurfaceChanged(GL10 gl, int width, int height) { if(height == 0) { height = 1; } gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity();
GLU.gluPerspective(gl, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } } |
다음은 웹서비스에 접근해서 렌더링에 필요한 데이터를 얻어오는 클래스이다.
BuildingInfoGeter.java |
import org.ksoap2.SoapEnvelope; import org.ksoap2.serialization.SoapObject; import org.ksoap2.serialization.SoapPrimitive; import org.ksoap2.serialization.SoapSerializationEnvelope; import org.ksoap2.transport.AndroidHttpTransport;
public class BuildingInfoGeter { private float[] vertices; private short[] indices;
private void RecvVerticesFromServerUseWebservice() { // // 웹서비스를 이용해 버텍스 집합을 얻어온다. // SoapObject Request = new SoapObject("http://tempuri.org/", "GetIFCVertices");
SoapSerializationEnvelope soapEnvelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); soapEnvelope.dotNet = true; soapEnvelope.setOutputSoapObject(Request);
AndroidHttpTransport aht = new AndroidHttpTransport ("http://192.168.34.74/IFCTest1/Service.asmx");
try { aht.call("http://tempuri.org/GetIFCVertices", soapEnvelope); SoapPrimitive resultString = (SoapPrimitive)soapEnvelope.getResponse();
MyStringToFloatParser stfp = new MyStringToFloatParser (resultString.toString());
int maxVerticesSize = stfp.GetMaxFloatDataSize(); vertices = new float[ maxVerticesSize ];
for( int i = 0 ; i < maxVerticesSize ; i++ ) { vertices[i] = stfp.GetFloatTypeItem(); } } catch (Exception e) { vertices = null; e.printStackTrace(); } }
private void RecvIndicesFromServerUseWebservice() { // // 웹서비스를 이용해 인덱스 집합을 얻어온다. // SoapObject Request = new SoapObject("http://tempuri.org/", "GetIFCIndices");
SoapSerializationEnvelope soapEnvelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
soapEnvelope.dotNet = true; soapEnvelope.setOutputSoapObject(Request);
AndroidHttpTransport aht = new AndroidHttpTransport ("http://192.168.34.74/IFCTest1/Service.asmx");
try { aht.call("http://tempuri.org/GetIFCIndices", soapEnvelope);
SoapPrimitive resultString = (SoapPrimitive)soapEnvelope.getResponse();
MyStringToFloatParser stfp = new MyStringToFloatParser (resultString.toString());
int maxIndicesSize = stfp.GetMaxFloatDataSize(); indices = new short[maxIndicesSize];
for( int i = 0 ; i < maxIndicesSize ; i++ ) { indices[i] = (short) stfp.GetFloatTypeItem(); } } catch (Exception e) { indices = null; e.printStackTrace(); } }
public BuildingInfoGeter() { RecvVerticesFromServerUseWebservice(); RecvIndicesFromServerUseWebservice(); }
public float[] GetVertices() { return vertices; }
public short[] GetIndices() { return indices; } } |
웹서비스를 통해 넘어오는 값들은 모두 문자열인데 이값을 파싱해 원하는 타입으로 변경 시켜줘야 한다. 이 작업을 하는 클래스가 MyStringToFloatParser이고 다음과 같이 구현 돼있다.
MyStringToFloatParser.java |
public class MyStringToFloatParser { private String stringData; private int nowPositon; private int maxFloatDataSize;
public MyStringToFloatParser(String stringData) { this.stringData = stringData; nowPositon = 0; maxFloatDataSize = (int)GetFloatTypeItem(); }
public int GetMaxFloatDataSize() { return maxFloatDataSize; }
public float GetFloatTypeItem() { String temp ="";
while( stringData.charAt(nowPositon) != ' ' ) { temp += stringData.charAt(nowPositon); nowPositon++; }
nowPositon++;
return Float.parseFloat(temp); } } |
마지막으로 빌딩 모델에 대한 렌더링 정보를 가지고 렌더링 방법을 정의, 렌더링하는 클래스를 작성한다.
MyBuildingModel.java |
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
public class MyBuildingModel { private FloatBuffer vertexBuffer; private ShortBuffer indexBuffer;
private float vertices[]; private short indices[];
public MyBuildingModel() { BuildingInfoGeter big = new BuildingInfoGeter(); vertices = big.GetVertices(); indices = big.GetIndices();
ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4); byteBuf.order(ByteOrder.nativeOrder()); vertexBuffer = byteBuf.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0);
byteBuf = ByteBuffer.allocateDirect(indices.length * 2 ); byteBuf.order(ByteOrder.nativeOrder()); indexBuffer = byteBuf.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0); }
public void draw(GL10 gl) { gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f); gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); gl.glDrawElements(GL10.GL_LINES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } } |
모든 구현이 완료 됐다. 이제 서버를 실행시키고 안드로이드 어플을 실행 시키면 다음과 같이 동작하는 것을 볼 수 있다.