Well here is what I have. The scrolling is done in the main window. I would like to create that type of map system but the calculations are far beyond what I would have imagined - they get complicated. You can zoom in on this map until cell sizes reach 256 pixels. My idea is that I never want the map to have borders. In other words if you zoom out far enough, I never want you to be able to see off of the map. My calculations are as follows. It works fine for computing the scrolling maxX and maxY, but when you zoom in, scroll down, and then zoom out.....everything goes to um...crap.
Code:
void CZeldaEditorView::UpdateScrollSizes()
{
//Set new scroll sizes based on map size and current cellsize
//or grid size
CSize size;
//Get pointer to document class for map info
CZeldaEditorDoc *pDoc=(CZeldaEditorDoc *)GetDocument();
size.cx=pDoc->m_iMapSizeX*m_iGridSize;
size.cy=pDoc->m_iMapSizeY*m_iGridSize;
SetScrollSizes(MM_TEXT,size);
//Retrieve width and height of client rect
CRect rect;
GetClientRect(&rect);
int mapx=pDoc->m_iMapSizeX;
int mapy=pDoc->m_iMapSizeY;
int maxx=pDoc->m_iMapSizeX*m_iGridSize;
int maxy=pDoc->m_iMapSizeY*m_iGridSize;
//Set max scroll
m_iMaxScrollX=(m_iGridSize*(mapx-1))-rect.Width();
m_iMaxScrollY=(m_iGridSize*mapy)-rect.Height();
//Don't show borders
if (m_iMaxScrollX<0) m_iMaxScrollX=0;
if (m_iMaxScrollY<0) m_iMaxScrollY=0;
//If map size won't fit in window because map is too small
//Re-adjust cell size so map fits perfectly in window
if (rect.Width()>=maxx-m_iGridSize)
{
m_iGridSize=rect.Width()/mapx;
m_iScrollX=-m_iMaxScrollX;
m_iScrollY=-m_iMaxScrollY;
}
}
//Transforms mouse clicks to always click on nearest tile
//to cursor regardless of zoom and scroll
void CZeldaEditorView::TransformMouseXY(CPoint mouse,CRect &rect,DWORD &offset)
{
//Find out remainder of m_iScrollX/m_iGridSize
int offsetx=(m_iScrollX % m_iGridSize);
int offsety=(m_iScrollY % m_iGridSize);
//Temporaries so I don't have to use mouse.x, mouse.y every
//time
int mx=mouse.x;
int my=mouse.y;
//Subtract mouse by offsets
mx-=offsetx;
my-=offsety;
//Snap to grid size
mx-=(mx % m_iGridSize);
my-=(my % m_iGridSize);
//Re-adjust on-screen rect to new grid rectangle
rect.left=mx+offsetx;
rect.top=my+offsety;
rect.right=mx+m_iGridSize+offsetx;
rect.bottom=my+m_iGridSize+offsety;
//Compute actual memory location of grid clicked
int MemoryX=(mouse.x - m_iScrollX)/m_iGridSize;
int MemoryY=(mouse.y - m_iScrollY)/m_iGridSize;
//Set offset in document
CZeldaEditorDoc *ptrDoc=(CZeldaEditorDoc *)GetDocument();
offset=MemoryY*ptrDoc->m_iMapSizeX+MemoryX;
}
Sorry for the mess but MFC code is a mess. Basically I'm adhering to the document/view architecture so that the document handles the info about the map and the view just draws the map according to the info. It's a real pain in the arse.
These calculations work but zooming only zooms in on the upper left corner. I know why it does this, but how would I zoom in on an area? Do I have to draw my map relative to the center of the screen, or can I just start at the upper left corner and set m_iScrollX and m_iScrollY to different values when zooming to get the zoom in on a point effect?
Hard to explain.
Maybe showing the actual OnDraw() will help.
Pardon the mess but it's a major work in progress.
Code:
void CZeldaEditorView::OnDraw(CDC* pDC)
{
//Pointer to document class
CZeldaEditorDoc* pDoc = (CZeldaEditorDoc *)GetDocument();
//Pointer to frame clasee
CMainFrame *ptrFrame=(CMainFrame *)AfxGetMainWnd();
//Number of maps (layers)
DWORD numMaps=pDoc->m_pMapManager->GetNumberOfMaps();
//Handle to destination DC
HDC dcDest=pDC->GetSafeHdc();
//Black brush for drawing
CBrush blackbrush;
blackbrush.CreateSolidBrush(RGB(0,0,0));
//Retrieve client rect
CRect ScreenRect;
GetClientRect(&ScreenRect);
//Fill it with black
pDC->FillRect(&ScreenRect,&blackbrush);
//Check for tile manager and map count
//Don't draw if null or maps=0
if (pDoc->m_pTileManager && numMaps>1)
{
//Tile size from bitmap
int TileSize=pDoc->m_iTileSize;
//Horizontal offset - debug info
int iOffsetHoriz=abs(m_iScrollX/m_iGridSize);
//Vertical offset - debug info
int iOffsetVert=abs(m_iScrollY/m_iGridSize);
//Function to compute relative to center of screenrect
//Algos don't work right with this yet
//ComputeStartGrid();
//Iterate through maps
//m_dwStartMapNum is starting map
//m_dwEndMapNum is ending map
//To view one layer set start to layer and end to start+1
for (DWORD map=m_dwStartMapNum;map<m_dwEndMapNum;map++)
{
//Get pointer to map
CMap *ptrMap=pDoc->m_pMapManager->GetMapClass(map);
//Get pointer to movement map - disabled
//CMap *ptrMove=pDoc->m_pMapManager->GetMapClass(0);
//Screen calculations for memory offset
int CurrentY=(-m_iScrollY/m_iGridSize);
int CurrentX=(-m_iScrollX/m_iGridSize);
//Remember CurrentX before it changes
int OriginX=CurrentX;
//Compute memory offset
DWORD offset=CurrentY*pDoc->m_iMapSizeX+CurrentX;
int offsetx=m_iScrollX % m_iGridSize;
int offsety=m_iScrollY % m_iGridSize;
//Save starting offset
DWORD startoffset=offset;
//Rect for grid display - if enabled
CRect GridRect;
//Draw the map finally
for (int i=offsety;i<ScreenRect.bottom;i+=m_iGridSize)
{
for (int j=offsetx;j<ScreenRect.right;j+=m_iGridSize)
{
//Retrieve ID from map
DWORD ID=ptrMap->GetValueAtOffset(offset);
//If this block is not empty
if (ID!=0xFFFFFFFF)
{
//Get DC of tile bitmap from tile manager
CDC *tempDC=pDoc->m_pTileManager->GetTileDC(ID);
//Get handle to tile bitmap DC
HDC dcSource=tempDC->GetSafeHdc();
//Get transparent color of tile
UINT dwTransColor=pDoc->m_pTileManager->GetTransColor(ID);
//Do a transparent blit
::TransparentBlt(dcDest,
j,i,
m_iGridSize,m_iGridSize,
dcSource,
0,0,
TileSize,TileSize,
dwTransColor);
}
//Draw the grid
GridRect.left=j;
GridRect.top=i;
GridRect.right=j+m_iGridSize;
GridRect.bottom=i+m_iGridSize;
//If view->Grid checked then draw grid
if (m_bGrid)
{
CBrush gridbrush;
gridbrush.CreateSolidBrush(RGB(255,255,255));
pDC->FrameRect(&GridRect,&gridbrush);
}
//Increment offset into memory
offset++;
//Increment cell column counter
CurrentX++;
//If column is at width of map break
if (CurrentX>=pDoc->m_iMapSizeX-1) break;
}
//Set column counter to origin
CurrentX=OriginX;
//Increment startoffset by one row
startoffset+=pDoc->m_iMapSizeX;
//Save startoffset
offset=startoffset;
//Increment row counter
CurrentY++;
//If no more rows, break
if (CurrentY>pDoc->m_iMapSizeY-1) break;
}
}
}
}
Now the reason that the OnDraw is so extensive is because I did not want to compute the memory offset inside of the loop to render the grid. That would be slow. So I pre-compute the memory offset - which is already taking into account the scrolling values and so it is at the right spot in memory to correctly display the grid. The entire visible portion of the map can be drawn without re-computing the offset. When the column counter reaches the map size OR the X value of the render reaches the right side of the client rectangle, the row counter is incremented, column counter is reset (originX at current is always 0), starting offset is incremented by one row (m_iMapSizeX), and current offset is set to the starting offset.
It'a a pain in the arse but it's a linear algorithm and it's quite fast.
The array is indexed in linear fashion and the render is done in linear fashion. The Tile manager holds all of the tiles. But get this. Instead of storing bitmaps each tile is an object with a device context that has been prepared beforehand so the image data is on the device context. Then blitting the image is as simple as using TransparentBlt from the API using the handles to the source DC and the dest DC. Simple.
It all works great. But zooming is a mess. The ComputeStartGrid() function is a function that can center the grid inside of the client rect, but then all of the LButtonDown and MouseMove() functions don't compute the mouse cursor to grid and mouse cursor to memory right.
If you'ev ever done a tile engine I'm sure you will understand in the editor you must transform mouse clicks to align with the displayed grid or you cannot place tiles correctly.
Also if the current tile is the selected tile and you click on the tile in the map, it erases it. The selected tile is stored in the m_pTileManager class and is set by the tile tool dialogs when you select a tile to use.
This hasn't been easy so forgive me if the algo sucks.