Related
Basically I've created a plane and rotate it 5 times to be a cube. I made cube with different colors of each side. And did some rotation with touch event.
Everthing was good so far, but the cube turned out to be like this.
Please help it's been driving me crazy!
My cube code:
public class PhotoCube{
public ArrayList<FloatBuffer> vertexBufferList = new ArrayList<>();
public int[][] lists = {
{0,1,0,0}, //front
{1,0,0,-90}, //top
{0,1,0,-90}, //left
{0,1,0,90}, //
{1,0,0,90}, //bottom
{0,1,0,180} //right
};
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static final float[] coords = { // in counterclockwise order:
0.5f, 0.5f, 0.5f, // top right
-0.5f, 0.5f, 0.5f, // top left
-0.5f, -0.5f, 0.5f, // bottom left
0.5f, -0.5f, 0.5f // bottom right
};
static final float[][] colorList = {
{1f,0f,0f,1f},
{0f,1f,0f,1f},
{1f,1f,1f,1f},
{1f,1f,0f,1f},
{1f,0f,1f,1f},
{0f,1f,1f,1f}
};
public PhotoCube() {
final int maxVertices = 6;
for(int[] list:lists){
float[] currentCoords = coords.clone();
for(int i=0;i<12;i+=3){
float x = coords[i];
float y = coords[i+1];
float z = coords[i+2];
double angle = Math.toRadians(list[3]);
currentCoords[i]=(float) ((list[0]==1)?x:(list[1]==1)?x*Math.cos(angle)+z*Math.sin(angle):x*Math.cos(angle)-y*Math.sin(angle));
currentCoords[i+1]=(float) ((list[0]==1)?y*Math.cos(angle)-z*Math.sin(angle):(list[1]==1)?y:x*Math.sin(angle)+y*Math.cos(angle));
currentCoords[i+2]=(float) ((list[0]==1)?z*Math.cos(angle)+y*Math.sin(angle):(list[1]==1)?z*Math.cos(angle)-x*Math.sin(angle):z);
}
ByteBuffer bb = ByteBuffer.allocateDirect(
// (number of coordinate values * 4 bytes per float)
currentCoords.length * 4);
// use the device hardware's native byte order
bb.order(ByteOrder.nativeOrder());
// create a floating point buffer from the ByteBuffer
FloatBuffer vertexBuffer = bb.asFloatBuffer();
// add the coordinates to the FloatBuffer
vertexBuffer.put(currentCoords);
// set the buffer to read the first coordinate
vertexBuffer.position(0);
if(vertexBufferList.size()==maxVertices){
vertexBufferList.remove(0);
}
vertexBufferList.add(vertexBuffer);
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
createProgram();
GLES20.glLinkProgram(mProgram);
// creates OpenGL ES program executables
}
}
public void createProgram(){
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// add the vertex shader to program
GLES20.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program
GLES20.glAttachShader(mProgram, fragmentShader);
}
public void draw(int order) {
final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
// Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
int positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(positionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, true,
vertexStride, vertexBufferList.get(order));
// get handle to fragment shader's vColor member
int colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
GLES20.glUniform4fv(colorHandle, 1, colorList[order], 0);
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
drawOrder.length,
GL_UNSIGNED_SHORT,
drawListBuffer);
// Disable vertex array
//GLES20.glDisableVertexAttribArray(positionHandle);
}
}
My Renderer code:
public class MyGLRenderer implements GLSurfaceView.Renderer {
private PhotoCube mPhotoCube;
public final float[] vPMatrix = new float[16];
private final float[] projectionMatrix = new float[16];
private final float[] viewMatrix = new float[16];
private int vPMatrixHandle = -1;
private volatile float mAngleX = 0;
private volatile float mAngleY = 0;
private float[] rotationMX = new float[16];
private float[] rotationMY = new float[16];
private float[] scratch = new float[16];
public MyGLRenderer(Context context){
}
public static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
mPhotoCube = new PhotoCube();
vPMatrixHandle = GLES20.glGetUniformLocation(mPhotoCube.mProgram, "uVPMatrix");
onDrawFrame(unused);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFuncSeparate(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA, GLES20.GL_ZERO, GLES20.GL_ONE);
Matrix.setRotateM(rotationMX, 0, -mAngleX, 0, 1, 0);
Matrix.setLookAtM(viewMatrix, 0, 0, 0, 5, 0f, 0f, -5f, 0f, 1.0f, 0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
Matrix.multiplyMM(scratch, 0, vPMatrix, 0, rotationMX, 0);
Matrix.setRotateM(rotationMY, 0, -mAngleY, scratch[0], scratch[4], scratch[8]);
Matrix.multiplyMM(scratch, 0, scratch, 0, rotationMY, 0);
vPMatrixHandle = GLES20.glGetUniformLocation(mPhotoCube.mProgram, "uMVPMatrix");
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, scratch, 0);
for(int i=0;i<mPhotoCube.lists.length;i++){
mPhotoCube.draw(i);
}
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
}
Here's my shader code
final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
"}";
final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
Thank you in advance.
You have to enable the Depth Test Depth test ensures that fragments that lie behind other fragments are discarded:
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
When you enable the depth test you must also clear the depth buffer:
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
However, if you have transparent objects and want to use Blending, the depth test must be disabled and you must draw the triangles of the meshes in sorted order from back to front. Also see OpenGL depth sorting
I am having some difficulties in correctly populating a CListCtrl with thumbnails of monitor displays.
On the right of my CDialog I have a static control and I render the image on a white canvas like this:
void CCenterCursorOnScreenDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if (nIDCtl == IDC_STATIC_MONITOR && !m_imgPreview.IsNull())
{
// Set the mode
SetStretchBltMode(lpDrawItemStruct->hDC, HALFTONE);
// Wipe the canvas
FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
// Get canvas rectangle
const CRect rectCanvas(lpDrawItemStruct->rcItem);
// Calculate ratio factors
const float nRatioImage = m_imgPreview.GetWidth() / static_cast<float>(m_imgPreview.GetHeight());
const float nRatioCanvas = rectCanvas.Width() / static_cast<float>(rectCanvas.Height());
// Calculate new rectangle size
// Account for portrait images (negative values)
CRect rectDraw = rectCanvas;
if (nRatioImage > nRatioCanvas)
rectDraw.SetRect(0, 0, rectDraw.right, static_cast<int>(rectDraw.right / nRatioImage));
else if (nRatioImage < nRatioCanvas)
rectDraw.SetRect(0, 0, static_cast<int>((rectDraw.bottom * nRatioImage)), rectDraw.bottom);
// Add a margin
rectDraw.DeflateRect(5, 5);
// Move to center
const CSize ptOffset = rectCanvas.CenterPoint() - rectDraw.CenterPoint();
rectDraw.OffsetRect(ptOffset);
// Add a black frame
FrameRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
// Draw
m_imgPreview.Draw(lpDrawItemStruct->hDC, rectDraw);
return;
}
CDialogEx::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
The above works beautifully:
But I have problems with the CListCtrl versions of the images. For instance, I am losing the colouring as you can see.
My CImageList is created like this:
m_ImageListThumb.Create(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, ILC_COLOR32, 0, 1);
m_ListThumbnail.SetImageList(&m_ImageListThumb, LVSIL_NORMAL);
I then create all the thumbnails by calling DrawThumbnails() in OnInitDialog:
void CCenterCursorOnScreenDlg::DrawThumbnails()
{
int monitorIndex = 0;
m_ListThumbnail.SetRedraw(FALSE);
for (auto& strMonitor : m_monitors.strMonitorNames)
{
CImage img;
CreateMonitorThumbnail(monitorIndex, img, true);
CBitmap* pImage = new CBitmap();
pImage->Attach((HBITMAP)img);
m_ImageListThumb.Add(pImage, nullptr);
CString strMonitorDesc = m_monitors.strMonitorNames.at(monitorIndex);
strMonitorDesc.AppendFormat(L" (Screen %d)", monitorIndex + 1);
m_ListThumbnail.InsertItem(monitorIndex, strMonitorDesc, monitorIndex);
monitorIndex++;
delete pImage;
}
m_ListThumbnail.SetRedraw(TRUE);
}
The CreateMonitorThumbnail function:
BOOL CCenterCursorOnScreenDlg::CreateMonitorThumbnail(const int iMonitorIndex, CImage &rImage, bool bSmall)
{
const CRect rcCapture = m_monitors.rcMonitors.at(iMonitorIndex);
// destroy the currently contained bitmap to create a new one
rImage.Destroy();
auto nWidth = rcCapture.Width();
auto nHeight = rcCapture.Height();
if (bSmall)
{
nWidth = THUMBNAIL_WIDTH;
nHeight = THUMBNAIL_HEIGHT;
}
// create bitmap and attach it to this object
if (!rImage.Create(nWidth, nHeight, 32, 0))
{
AfxMessageBox(L"Cannot create image!", MB_ICONERROR);
return FALSE;
}
// create virtual screen DC
CDC dcScreen;
dcScreen.CreateDC(_T("DISPLAY"), nullptr, nullptr, nullptr);
// copy the contents from the virtual screen DC
BOOL bRet = FALSE;
if (bSmall)
{
CRect rt(0, 0, nWidth, nHeight);
//::FillRect(rImage.GetDC(), rt, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
bRet = ::StretchBlt(rImage.GetDC(), 0, 0,
nWidth,
nHeight,
dcScreen.m_hDC,
rcCapture.left,
rcCapture.top,
rcCapture.Width(),
rcCapture.Height(), SRCCOPY | CAPTUREBLT);
}
else
{
bRet = ::BitBlt(rImage.GetDC(), 0, 0,
rcCapture.Width(),
rcCapture.Height(),
dcScreen.m_hDC,
rcCapture.left,
rcCapture.top, SRCCOPY | CAPTUREBLT);
}
// do cleanup and return
dcScreen.DeleteDC();
rImage.ReleaseDC();
return bRet;
}
Ideally I want to have exactly the same kind of visual image as on the right, but obviously resized down. How do I fix this?
I simplified the converting from CImage to CBitmap but it made no difference:
void CCenterCursorOnScreenDlg::DrawThumbnails()
{
int monitorIndex = 0;
// Stop redrawing the CListCtrl
m_ListThumbnail.SetRedraw(FALSE);
// Loop monitor info
for (auto& strMonitor : m_monitors.strMonitorNames)
{
// Create the thumbnail image
CImage monitorThumbnail;
CreateMonitorThumbnail(monitorIndex, monitorThumbnail, true);
// Convert it to a CBitmap
CBitmap* pMonitorThumbnailBitmap = CBitmap::FromHandle(monitorThumbnail);
// Add the CBitmap to the CImageList
m_ImageListThumb.Add(pMonitorThumbnailBitmap, nullptr);
// Build the caption description
CString strMonitorDesc = m_monitors.strMonitorNames.at(monitorIndex);
strMonitorDesc.AppendFormat(L" (Screen %d)", monitorIndex + 1);
// Add the item to the CListCtrl
m_ListThumbnail.InsertItem(monitorIndex, strMonitorDesc, monitorIndex);
monitorIndex++;
}
// Start redrawiung the CListCtrl again
m_ListThumbnail.SetRedraw(TRUE);
}
If I change my code to pass false for the last parameter, so that it uses the original captured images without scaling down:
The colours are god there, so it is when I do:
if (bSmall)
{
CRect rt(0, 0, nWidth, nHeight);
//::FillRect(rImage.GetDC(), rt, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
bRet = ::StretchBlt(rImage.GetDC(), 0, 0,
nWidth,
nHeight,
dcScreen.m_hDC,
rcCapture.left,
rcCapture.top,
rcCapture.Width(),
rcCapture.Height(), SRCCOPY | CAPTUREBLT);
}
that it messes up.
My issue was did not have anything to do with OnDrawItem. I simply included that to indicate how the image on the right was being rendered. I thought it may helped as background information. But it has probably confused the question and I may take it out in the long run!
Based on the comments I was reminded about SetStretchBltMode which was missing from CreateMonitorThumbnail. So, I now have this function:
BOOL CCenterCursorOnScreenDlg::CreateMonitorThumbnail(const int iMonitorIndex, CImage &rImage, bool bResizeAsThumbnail)
{
const CRect rcCapture = m_monitors.rcMonitors.at(iMonitorIndex);
// Destroy the currently contained bitmap to create a new one
rImage.Destroy();
// Massage the dimensions as we want a thumbnail
auto nWidth = rcCapture.Width();
auto nHeight = rcCapture.Height();
if (bResizeAsThumbnail)
{
nWidth = m_iThumbnailWidth;
auto dRatio = rcCapture.Width() / nWidth;
//nHeight = m_iThumbnailHeight;
nHeight = static_cast<int>(rcCapture.Height() / dRatio);
if (nHeight > m_iThumbnailHeight)
{
AfxMessageBox(L"Need to investigate!");
}
}
// Create bitmap and attach it to this object
if (!rImage.Create(nWidth, nHeight, 32, 0))
{
AfxMessageBox(L"Cannot create image!", MB_ICONERROR);
return FALSE;
}
// Create virtual screen DC
CDC dcScreen;
dcScreen.CreateDC(L"DISPLAY", nullptr, nullptr, nullptr);
// Copy (or resize) the contents from the virtual screen DC
BOOL bRet = FALSE;
auto dcImage = rImage.GetDC();
if (bResizeAsThumbnail)
{
// Set the mode first!
SetStretchBltMode(dcImage, COLORONCOLOR);
CPen penBlack;
penBlack.CreatePen(PS_SOLID, 3, RGB(0, 0, 0));
::Rectangle(dcImage, 0, 0, m_iThumbnailWidth, m_iThumbnailHeight);
int iTop = (m_iThumbnailHeight - nHeight) / 2;
// Copy (and resize)
bRet = ::StretchBlt(dcImage, 0, iTop,
nWidth,
nHeight,
dcScreen.m_hDC,
rcCapture.left,
rcCapture.top,
rcCapture.Width(),
rcCapture.Height(), SRCCOPY | CAPTUREBLT);
}
else
{
// Copy
bRet = ::BitBlt(dcImage, 0, 0,
rcCapture.Width(),
rcCapture.Height(),
dcScreen.m_hDC,
rcCapture.left,
rcCapture.top, SRCCOPY | CAPTUREBLT);
}
// Do cleanup and return
dcScreen.DeleteDC();
rImage.ReleaseDC();
return bRet;
}
That was the key to getting the thumbnail showing with the right colours:
How can I set my menu Sprites in the center of the screen with the usual look for menu
CCSize size = CCDirector::sharedDirector()->getWinSize();
sprite ->setPosition(ccp(size.width, size.height));
Please help me with the (ccp(?,?))
Sprites: Start, Options, Quit for game menu
#include "MenuScene.h"
#include "cocos2d.h"
USING_NS_CC;
using namespace cocos2d;
CCSprite *car;
CCScene* MenuScene::scene()
{
CCScene *scene = CCScene::create();
MenuScene *layer = MenuScene::create();
scene->addChild(layer);
return scene;
}
// on "init" you need to initialize your instance
bool MenuScene::init()
{
// 1. super init first
if ( !CCLayer::init() )
{
return false;
}
this->setTouchEnabled(true);
//this->schedule( schedule_selector(MenuScene::update) );
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
CCSize size = CCDirector::sharedDirector()->getWinSize();
CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
// add a "close" icon to exit the progress. it's an autorelease object
// CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
// "CloseNormal.png",
// "CloseSelected.png",
// this,
// menu_selector(MenuScene::menuCloseCallback));
//
// pCloseItem->setPosition(ccp(origin.x + visibleSize.width -
pCloseItem->getContentSize().width ,
// origin.y +
pCloseItem->getContentSize().height));
// create menu, it's an autorelease object
// CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
// pMenu->setPosition(CCPointZero);
// this->addChild(pMenu, 1);
// 3. add sprite below...
CCSprite *menuLayout = CCSprite::create("sky.png");
menuLayout->setPosition(ccp(size.width/2, size.height/2));
this->addChild(menuLayout, -1);
float aX = size.width / menuLayout->getContentSize().width;
float aY = size.height / menuLayout->getContentSize().height;
menuLayout->setScaleX(aX);
menuLayout->setScaleY(aY);
return true;
}
//callfuncN_selector(MainScene::spriteMoveFinished)
//backcalls the function spriteMoveFinished()
void MenuScene::spriteMoveFinished(CCNode* pSender)
{
CCSprite *sprite = (CCSprite *)pSender;
this->removeChild(sprite, true);
}
void MenuScene::Menus()
{
CCSize size = CCDirector::sharedDirector()->getWinSize();
CCMenuItemImage *startItem = CCMenuItemImage::create("sprite.png",
"sprite.png",this,menu_selector(MenuScene::menuCloseCallback));
startItem->setPosition(ccp(590, 450));
float aX = size.width / startItem->getContentSize().width;
float aY = size.height / startItem->getContentSize().height;
// startItem->setScaleX(aX);
// startItem->setScaleY(aY);
CCMenuItemImage *extraItem = CCMenuItemImage::create("sprite2.png",
"sprite2.png",this,menu_selector(MenuScene::menuCloseCallback));
extraItem->setPosition(ccp(590,350));
//float bX = size.width / extraItem->getContentSize().width;
//float bY = size.height / extraItem->getContentSize().height;
//extraItem->setScaleX(bX);
//extraItem->setScaleY(bY);
CCMenuItemImage *optionsItem = CCMenuItemImage::create("options.png",
"options.png",this,menu_selector(MenuScene::menuCloseCallback));
optionsItem->setPosition(ccp(590, 250));
//float cX = size.width / optionsItem->getContentSize().width;
//float cY = size.height / optionsItem->getContentSize().height;
//optionsItem->setScaleX(cX);
//optionsItem->setScaleY(cY);\
CCMenuItemImage *quitItem = CCMenuItemImage::create("quit.png",
"quit.png",this,menu_selector(MenuScene::menuCloseCallback));
quitItem->setPosition(ccp(590, 150));
// float dX = size.width / quitItem->getContentSize().width;
// float dY = size.height / quitItem->getContentSize().height;
// quitItem->setScaleX(dX);
// quitItem->setScaleY(dY);
CCMenu* pMenu = CCMenu::create(startItem, extraItem, optionsItem, quitItem,
NULL);
pMenu->setPosition(0,0);
this->addChild(pMenu, 0);
}
void MenuScene::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event){
Menus();
}
void MenuScene::ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event){
Menus();
}
void MenuScene::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event){
}
void MenuScene::ccTouchesCancelled(cocos2d::CCSet* touches, cocos2d::CCEvent*
event){
}
void MenuScene::menuCloseCallback(CCObject* pSender)
{
CCDirector::sharedDirector()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
/* namespace cocos2d */
If you wish to position the menu at the center of the screen you must provide the center co-ordinates in ccl
sprite ->setPosition(ccp(size.width/2, size.height/2));
First you get window size...
CGSize Window_size=[[CCDirector sharedDirector]winSize];
Now set CCMenuItem in CCMenu With position...
CCMenuItem *Minus_Menu_item = [CCMenuItemImage itemWithNormalImage:#"ButtonMinus.png" selectedImage:#"ButtonMinus.png" disabledImage:#"ButtonNum1.png" target:self selector:#selector(Minus_Button:)];
Minus_Menu_item.position=ccp(180, 200);
Menu_Minus=[CCMenu menuWithItems:Minus_Menu_item, nil];
Menu_Minus.position = CGPointZero;
[self addChild:Menu_Minus];
UPDATE : By the help of #datenwolf I know that the return value of gluBuild2DMipmaps is not the pointer to the texture, instead it's only an error code. I forgot to call glGenTextures and glBindTexture. Look in the method LoadTextureRaw in this answer
I have a problem when rendering multiple object, which each having their own Texture file definition, that is, they all draw the same texture. I create a class hierarchy, CDrawObject->CBall. In the CDrawObject, I define this :
public ref class CDrawObject
{
protected:
BYTE * dataTexture;
GLuint * texture;
public:
String ^ filename;
CDrawObject(void);
virtual void draw();
void LoadTextureRaw();
};
In the LoadTextureDraw(), I define this:
void CDrawObject::LoadTextureRaw()
{
//GLuint texture;
if(!filename) return;
if(filename->Equals("")) return;
texture = new GLuint;
System::Drawing::Bitmap ^ bitmap = gcnew Bitmap(filename);
int h = bitmap->Height;
int w = bitmap->Width;
int s = w * h;
dataTexture = new BYTE[s * 3];
System::Drawing::Rectangle rect = System::Drawing::Rectangle(0,0,w,h);
System::Drawing::Imaging::BitmapData ^ bitmapData =
bitmap->LockBits(rect,System::Drawing::Imaging::ImageLockMode::ReadWrite , System::Drawing::Imaging::PixelFormat::Format24bppRgb);
::memcpy(dataTexture,bitmapData->Scan0.ToPointer(),s*3);
/* old code
bitmap->UnlockBits(bitmapData);
pin_ptr<GLuint*> pt = &texture;//pin managed pointer, to be unmanaged
**pt = gluBuild2DMipmaps(GL_TEXTURE_2D, 3, w,h,GL_BGR_EXT, GL_UNSIGNED_BYTE, dataTexture);
*/
//new code : working fine this way. I forgot to call glGenTextures and glBindTexture
bitmap->UnlockBits(bitmapData);
pin_ptr<GLuint*> pt = &texture;//pin managed pointer, to be unmanaged... a must here :)
glEnable(GL_TEXTURE_2D);
glGenTextures(1,*pt);
glBindTexture(GL_TEXTURE_2D,**pt);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, w,h,GL_BGR_EXT, GL_UNSIGNED_BYTE, dataTexture);
}
And as the CBall:draw itself, I define this :
void CBall::draw(){
glLoadIdentity();
if(texture!=NULL && !filename->Equals(""))
{
glEnable(GL_TEXTURE_2D);
pin_ptr<GLuint*> pt = &texture;
glBindTexture(GL_TEXTURE_2D,**pt);
}
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GL_NORMALIZE);
glTranslatef(this->x,this->y,this->z);
glRotatef(this->sudut_rotasi_x,1,0,0);
glRotatef(this->sudut_rotasi_y,0,1,0);
glRotatef(this->sudut_rotasi_z,0,0,1);
glScalef(this->x_scale,this->y_scale,this->z_scale);
GLUquadricObj *q = gluNewQuadric();
gluQuadricNormals(q, GL_SMOOTH);
gluQuadricTexture(q, GL_TRUE);
gluSphere(q, r, 32, 16);
glFlush();
glDisable(GL_TEXTURE_2D);
}
The problem is, when I draw two (or more) ball object, they all drawn using the same texture. I already debug the code, and for each object, they all have different texture variable. Here is a snapshot of my code that draw those balls :
...
CBall ^ ball = gcnew CBall();
ball->x=Convert::ToSingle(r->GetAttribute("x"));
ball->y=Convert::ToSingle(r->GetAttribute("y"));
ball->z=Convert::ToSingle(r->GetAttribute("z"));
ball->r=Convert::ToSingle(r->GetAttribute("r"));
ball->filename=r->GetAttribute("filename");
ball->LoadTextureRaw();
addGraphic(id, ball);
...
Those code were called from a read XML file method.
What did I do wrong with this OpenGL Code?
Your problem is, that gluBuild2DMipmaps doesn't return the texture name, but a error code. You need to create a texture name separately.
Try this:
public ref class CDrawObject
{
protected:
GLuint texture; // just a GLuint, not a pointer!
public:
String ^ filename;
CDrawObject(void);
virtual void draw();
void LoadTextureRaw();
};
Change LoadTextureRaw a bit:
void CDrawObject::LoadTextureRaw()
{
if(!filename)
return;
if(filename->Equals(""))
return;
System::Drawing::Bitmap ^ bitmap = gcnew Bitmap(filename);
int h = bitmap->Height;
int w = bitmap->Width;
int s = w * h;
System::Drawing::Rectangle rect = System::Drawing::Rectangle(0,0,w,h);
System::Drawing::Imaging::BitmapData ^ bitmapData =
bitmap->LockBits( rect,
System::Drawing::Imaging::ImageLockMode::ReadWrite,
System::Drawing::Imaging::PixelFormat::Format24bppRgb );
// This is the important part: We generate a texture name and...
glGenTextures(1, &texture); // this should not require a pin_ptr, after all were in the middle of a member function of the class, so the garbage collector will not kick in.
// ...bind it, causing creation of a (yet uninitialized) texture object
glBindTexture(GL_TEXTURE_2D, texture);
GLint error = gluBuild2DMipmaps(
GL_TEXTURE_2D,
GL_RGB, // this should be a valid OpenGL token, not the number of components!
w, h,
GL_BGR_EXT, GL_UNSIGNED_BYTE,
bitmapData->Scan0.ToPointer() );
bitmap->UnlockBits(bitmapData);
}
Finally draw (which I rearranged a little)
void CBall::draw(){
glLoadIdentity();
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GL_NORMALIZE);
glTranslatef(this->x,this->y,this->z);
glRotatef(this->sudut_rotasi_x,1,0,0);
glRotatef(this->sudut_rotasi_y,0,1,0);
glRotatef(this->sudut_rotasi_z,0,0,1);
glScalef(this->x_scale,this->y_scale,this->z_scale);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture);
GLUquadricObj *q = gluNewQuadric();
gluQuadricNormals(q, GL_SMOOTH);
gluQuadricTexture(q, GL_TRUE);
gluSphere(q, r, 32, 16);
// glFlush is not required
glDisable(GL_TEXTURE_2D);
}
My app launches with a view controller and a simple view consisting of a button and a subview. When the user touches the button, the subview is populated with scrollviews that display the column headers, row headers, and cells of a spreadsheet. To draw the cells, I use CGBitmapContext to draw the cells, generate an image, and then put the image into the imageview contained in the scrollview that displays the cells.
When I run the app on the iPad, it displays the cells just fine, and the scrollview lets the user scroll around in the spreadsheet without any problems. If the user touches the button a second time, the spreadsheet redraws and continues to work perfectly, If, however, the user touches the button a third time, the app crashes. There is no exception information display in the Application Output window.
My first thought was that the successive button pushes were using up all the available memory, so I overrode the DidReceiveMemoryWarning method in the view controller and used a breakpoint to confirm that this method was not getting called. My next thought was that the CGBitmapContext was not getting released and looked for a Monotouch equivalent of Objective C's CGContextRelease() function. The closest I could find was the CGBitmapContext instance method Dispose(), which I called, without solving the problem.
In order to free up as much memory as possible (in case I was somehow running out of memory without tripping a warning), I tried forcing garbage collection each time I finished using a CGBitmapContext. This made the problem worse. Now the program would crash moments after displaying the spreadsheet the first time. This caused me to wonder whether the Garbage Collector was somehow collecting something necessary to the continued display of graphics on the screen.
I would be grateful for any suggestions on further avenues to investigate for the cause of these crashes. I have included the source code for the SpreadsheetView class. The relevant method is DrawSpreadsheet(), which is called when the button is touched.
Thank you for your assistance on this matter.
Stephen Ashley
public class SpreadsheetView : UIView
{
public ISpreadsheetMessenger spreadsheetMessenger = null;
public UIScrollView cellsScrollView = null;
public UIImageView cellsImageView = null;
public SpreadsheetView(RectangleF frame) : base()
{
Frame = frame;
BackgroundColor = Constants.backgroundBlack;
AutosizesSubviews = true;
}
public void DrawSpreadsheet()
{
UInt16 RowHeaderWidth = spreadsheetMessenger.RowHeaderWidth;
UInt16 RowHeaderHeight = spreadsheetMessenger.RowHeaderHeight;
UInt16 RowCount = spreadsheetMessenger.RowCount;
UInt16 ColumnHeaderWidth = spreadsheetMessenger.ColumnHeaderWidth;
UInt16 ColumnHeaderHeight = spreadsheetMessenger.ColumnHeaderHeight;
UInt16 ColumnCount = spreadsheetMessenger.ColumnCount;
// Add the corner
UIImageView cornerView = new UIImageView(new RectangleF(0f, 0f,
RowHeaderWidth, ColumnHeaderHeight));
cornerView.BackgroundColor = Constants.headingColor;
CGColorSpace cornerColorSpace = null;
CGBitmapContext cornerContext = null;
IntPtr buffer = Marshal.AllocHGlobal(RowHeaderWidth * ColumnHeaderHeight * 4);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory.");
try
{
cornerColorSpace = CGColorSpace.CreateDeviceRGB();
cornerContext = new CGBitmapContext
(buffer, RowHeaderWidth, ColumnHeaderHeight, 8, 4 * RowHeaderWidth,
cornerColorSpace, CGImageAlphaInfo.PremultipliedFirst);
cornerContext.SetFillColorWithColor(Constants.headingColor.CGColor);
cornerContext.FillRect(new RectangleF(0f, 0f, RowHeaderWidth, ColumnHeaderHeight));
cornerView.Image = UIImage.FromImage(cornerContext.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (cornerContext != null)
{
cornerContext.Dispose();
cornerContext = null;
}
if (cornerColorSpace != null)
{
cornerColorSpace.Dispose();
cornerColorSpace = null;
}
}
cornerView.Image = DrawBottomRightCorner(cornerView.Image);
AddSubview(cornerView);
// Add the cellsScrollView
cellsScrollView = new UIScrollView
(new RectangleF(RowHeaderWidth, ColumnHeaderHeight,
Frame.Width - RowHeaderWidth,
Frame.Height - ColumnHeaderHeight));
cellsScrollView.ContentSize = new SizeF
(ColumnCount * ColumnHeaderWidth,
RowCount * RowHeaderHeight);
Size iContentSize = new Size((int)cellsScrollView.ContentSize.Width,
(int)cellsScrollView.ContentSize.Height);
cellsScrollView.BackgroundColor = UIColor.Black;
AddSubview(cellsScrollView);
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
CGGradient gradient = null;
UIImage image = null;
int bytesPerRow = 4 * iContentSize.Width;
int byteCount = bytesPerRow * iContentSize.Height;
buffer = Marshal.AllocHGlobal(byteCount);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory.");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
context = new CGBitmapContext
(buffer, iContentSize.Width,
iContentSize.Height, 8, 4 * iContentSize.Width,
colorSpace, CGImageAlphaInfo.PremultipliedFirst);
float[] components = new float[]
{.75f, .75f, .75f, 1f,
.25f, .25f, .25f, 1f};
float[] locations = new float[]{0f, 1f};
gradient = new CGGradient(colorSpace, components, locations);
PointF startPoint = new PointF(0f, (float)iContentSize.Height);
PointF endPoint = new PointF((float)iContentSize.Width, 0f);
context.DrawLinearGradient(gradient, startPoint, endPoint, 0);
context.SetLineWidth(Constants.lineWidth);
context.BeginPath();
for (UInt16 i = 1; i <= RowCount; i++)
{
context.MoveTo
(0f, iContentSize.Height - i * RowHeaderHeight + (Constants.lineWidth/2));
context.AddLineToPoint((float)iContentSize.Width,
iContentSize.Height - i * RowHeaderHeight + (Constants.lineWidth/2));
}
for (UInt16 j = 1; j <= ColumnCount; j++)
{
context.MoveTo((float)j * ColumnHeaderWidth - Constants.lineWidth/2,
(float)iContentSize.Height);
context.AddLineToPoint((float)j * ColumnHeaderWidth - Constants.lineWidth/2, 0f);
}
context.StrokePath();
image = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (gradient != null)
{
gradient.Dispose();
gradient = null;
}
if (context != null)
{
context.Dispose();
context = null;
}
if (colorSpace != null)
{
colorSpace.Dispose();
colorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
UIImage finalImage = ActivateCell(1, 1, image);
finalImage = ActivateCell(0, 0, finalImage);
cellsImageView = new UIImageView(finalImage);
cellsImageView.Frame = new RectangleF(0f, 0f,
iContentSize.Width, iContentSize.Height);
cellsScrollView.AddSubview(cellsImageView);
}
private UIImage ActivateCell(UInt16 column, UInt16 row, UIImage backgroundImage)
{
UInt16 ColumnHeaderWidth = (UInt16)spreadsheetMessenger.ColumnHeaderWidth;
UInt16 RowHeaderHeight = (UInt16)spreadsheetMessenger.RowHeaderHeight;
CGColorSpace cellColorSpace = null;
CGBitmapContext cellContext = null;
UIImage cellImage = null;
IntPtr buffer = Marshal.AllocHGlobal(4 * ColumnHeaderWidth * RowHeaderHeight);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: ActivateCell()");
try
{
cellColorSpace = CGColorSpace.CreateDeviceRGB();
// Create a bitmap the size of a cell
cellContext = new CGBitmapContext
(buffer, ColumnHeaderWidth, RowHeaderHeight, 8,
4 * ColumnHeaderWidth, cellColorSpace, CGImageAlphaInfo.PremultipliedFirst);
// Paint it white
cellContext.SetFillColorWithColor(UIColor.White.CGColor);
cellContext.FillRect(new RectangleF(0f, 0f, ColumnHeaderWidth, RowHeaderHeight));
// Convert it to an image
cellImage = UIImage.FromImage(cellContext.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (cellContext != null)
{
cellContext.Dispose();
cellContext = null;
}
if (cellColorSpace != null)
{
cellColorSpace.Dispose();
cellColorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
// Draw the border on the cell image
cellImage = DrawBottomRightCorner(cellImage);
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
Size iContentSize = new Size((int)backgroundImage.Size.Width,
(int)backgroundImage.Size.Height);
buffer = Marshal.AllocHGlobal(4 * iContentSize.Width * iContentSize.Height);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: ActivateCell().");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
// Set up a bitmap context the size of the whole grid
context = new CGBitmapContext
(buffer, iContentSize.Width,
iContentSize.Height, 8, 4 * iContentSize.Width,
colorSpace, CGImageAlphaInfo.PremultipliedFirst);
// Draw the original grid into the bitmap
context.DrawImage(new RectangleF(0f, 0f, iContentSize.Width, iContentSize.Height),
backgroundImage.CGImage);
// Draw the cell image into the bitmap
context.DrawImage(new RectangleF(column * ColumnHeaderWidth,
iContentSize.Height - (row + 1) * RowHeaderHeight,
ColumnHeaderWidth, RowHeaderHeight),
cellImage.CGImage);
// Convert the bitmap back to an image
backgroundImage = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (context != null)
{
context.Dispose();
context = null;
}
if (colorSpace != null)
{
colorSpace.Dispose();
colorSpace = null;
}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
return backgroundImage;
}
private UIImage DrawBottomRightCorner(UIImage image)
{
int width = (int)image.Size.Width;
int height = (int)image.Size.Height;
float lineWidth = Constants.lineWidth;
CGColorSpace colorSpace = null;
CGBitmapContext context = null;
UIImage returnImage = null;
IntPtr buffer = Marshal.AllocHGlobal(4 * width * height);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException("Out of memory: DrawBottomRightCorner().");
try
{
colorSpace = CGColorSpace.CreateDeviceRGB();
context = new CGBitmapContext
(buffer, width, height, 8, 4 * width, colorSpace,
CGImageAlphaInfo.PremultipliedFirst);
context.DrawImage(new RectangleF(0f, 0f, width, height),
image.CGImage);
context.BeginPath();
context.MoveTo(0f, (int)(lineWidth/2f));
context.AddLineToPoint(width - (int)(lineWidth/2f), (int)(lineWidth/2f));
context.AddLineToPoint(width - (int)(lineWidth/2f), height);
context.SetLineWidth(Constants.lineWidth);
context.SetStrokeColorWithColor(UIColor.Black.CGColor);
context.StrokePath();
returnImage = UIImage.FromImage(context.ToImage());
}
finally
{
Marshal.FreeHGlobal(buffer);
if (context != null){
context.Dispose();
context = null;}
if (colorSpace != null){
colorSpace.Dispose();
colorSpace = null;}
// GC.Collect();
//GC.WaitForPendingFinalizers();
}
return returnImage;
}
}
Not sure if this will solve your problem (I'm even newer to this than you are), but it seems from this answer that MonoTouch prefers a different paradigm for creating/releasing graphics contexts, along the lines of:
UIGraphics.BeginImageContext(rect.Size)
var context = UIContext.GetCurrentContext();
// ... do stuff ...
UIImage image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
// ... do something with image ...
I don't know if it's releasing everything properly, but otherwise it seems to work.