generated from sigonasr2/CPlusPlusProjectTemplate
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
682 lines
22 KiB
682 lines
22 KiB
#include "olcUTIL_Geometry2D.h"
|
|
#include "TSXParser.h"
|
|
#include "olcPGEX_TransformedView.h"
|
|
#include "olcUTIL_Camera2D.h"
|
|
#include "olcPGEX_QuickGUI.h"
|
|
#include <variant>
|
|
#include <deque>
|
|
|
|
using namespace olc;
|
|
using namespace olc::utils;
|
|
using namespace QuickGUI;
|
|
|
|
const std::string TILESET_DIR="./Tiles/";
|
|
|
|
class TiledCollisionEditor : public olc::PixelGameEngine
|
|
{
|
|
Tileset currentTileset;
|
|
Renderable mapImage;
|
|
std::string activeTileset;
|
|
Quadrilateral*editingQuad=nullptr;
|
|
Quadrilateral originalQuad;
|
|
std::string selectedObj="";
|
|
int editingPoint=4; //0-3 for the index we are editing within editingQuad.
|
|
bool dragging=false;
|
|
bool dragTranslate=false;
|
|
vf2d upperLeftDragOffset{};
|
|
Quadrilateral*highlightedQuad=nullptr;
|
|
std::string nameEditObj="";
|
|
size_t lastSelectedItem=0;
|
|
|
|
bool dragNewObj=false;
|
|
vi2d upperLeftObjTile{};
|
|
vi2d lowerRightObjTile{};
|
|
|
|
bool selectingFile=false;
|
|
|
|
Renderable circle;
|
|
Renderable createNewButtonImg;
|
|
Renderable undoButtonImg;
|
|
Renderable redoButtonImg;
|
|
Renderable editButtonImg;
|
|
|
|
bool loadedFirstFile=false;
|
|
|
|
TransformedView view;
|
|
|
|
std::vector<std::string>tilesetList;
|
|
|
|
std::unordered_map<std::string,TilesetObject>previousObjState;
|
|
|
|
std::deque<std::unordered_map<std::string,TilesetObject>>redoList;
|
|
std::deque<std::unordered_map<std::string,TilesetObject>>undoList;
|
|
|
|
public:
|
|
TiledCollisionEditor()
|
|
{
|
|
sAppName = "TiledCollisionEditor";
|
|
}
|
|
|
|
Manager gui;
|
|
Manager selectionGui;
|
|
|
|
ImageCheckBox*createNewButton=nullptr;
|
|
ImageCheckBox*editButton=nullptr;
|
|
ImageButton*undoButton=nullptr;
|
|
ImageButton*redoButton=nullptr;
|
|
TextBox*nameBox=nullptr;
|
|
Button*openButton=nullptr;
|
|
ListBox*tilesetsList=nullptr;
|
|
TSXParser parsedMap{""};
|
|
Button*closeButton=nullptr;
|
|
public:
|
|
bool OnUserCreate() override
|
|
{
|
|
SetFontSprite("font3.png");
|
|
|
|
circle.Create(5,5);
|
|
SetDrawTarget(circle.Sprite());
|
|
Clear(BLANK);
|
|
FillCircle({2,2},2);
|
|
SetDrawTarget(nullptr);
|
|
circle.Decal()->Update();
|
|
|
|
createNewButtonImg.Load("newCollisionButton.png");
|
|
editButtonImg.Load("EditButton.png");
|
|
undoButtonImg.Load("undoButton.png");
|
|
redoButtonImg.Load("redoButton.png");
|
|
|
|
createNewButton=new ImageCheckBox{gui,createNewButtonImg,false,vf2d{4.f,ScreenHeight()-36.f},{32.f,32.f},{4,1},{32,32}};
|
|
createNewButton->hotkey=Q;
|
|
editButton=new ImageCheckBox{gui,editButtonImg,true,vf2d{40.f,ScreenHeight()-36.f},{32.f,32.f},{4,4},{32,32}};
|
|
editButton->hotkey=E;
|
|
undoButton=new ImageButton{gui,undoButtonImg,vf2d{ScreenWidth()-72.f,ScreenHeight()-36.f},{32.f,32.f},{4,4},{32,32}};
|
|
redoButton=new ImageButton{gui,redoButtonImg,vf2d{ScreenWidth()-36.f,ScreenHeight()-36.f},{32.f,32.f},{4,4},{32,32}};
|
|
|
|
nameBox=new TextBox{gui,"",vf2d{76.f,ScreenHeight()-36.f+6.f},{128,20.f},{1,1}};
|
|
nameBox->bHasBackground=true;
|
|
|
|
openButton=new Button{gui,"Open",{ScreenWidth()-32.f,0.f},{32,12.f},{0.5f,0.5f}};
|
|
closeButton=new Button{selectionGui,"Close",{ScreenWidth()-32.f,0.f},{32,12.f},{0.5f,0.5f}};
|
|
|
|
std::filesystem::path dir{TILESET_DIR};
|
|
|
|
for(auto const&dir:std::filesystem::directory_iterator(dir)){
|
|
if(dir.path().string().ends_with(".tsx")){
|
|
tilesetList.push_back(dir.path().string());
|
|
}
|
|
}
|
|
|
|
tilesetsList=new ListBox{selectionGui,tilesetList,{ScreenWidth()/2-240.f,ScreenHeight()/2-60.f},{480.f,120.f},16.f};
|
|
|
|
previousObjState=currentTileset.objects;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SaveFile(bool ignoreUndoEntry=false){
|
|
if(!ignoreUndoEntry){
|
|
undoList.push_back(previousObjState);
|
|
previousObjState=currentTileset.objects;
|
|
redoList.clear();
|
|
if(undoList.size()>20)undoList.pop_front();
|
|
}
|
|
std::stringstream file;
|
|
if(file.good()){
|
|
const std::vector<XMLTag>originalData=parsedMap.originalData;
|
|
const std::vector<NonObject>nonObjects=parsedMap.nonObjects;
|
|
auto xmlTag=std::find_if(originalData.begin(),originalData.end(),[](const XMLTag tag){return tag.tag=="?xml";});
|
|
if(xmlTag!=originalData.end())file<<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"<<std::endl;
|
|
|
|
const Tileset&activeSet=currentTileset;
|
|
file<<std::format("<tileset version=\"1.10\" tiledversion=\"1.10.2\" name=\"{}\" tilewidth=\"{}\" tileheight=\"{}\" tilecount=\"{}\" columns=\"{}\">",
|
|
activeSet.name,activeSet.tilewidth,activeSet.tileheight,activeSet.tilecount,activeSet.columns)<<std::endl;
|
|
|
|
auto transformationsTag=std::find_if(originalData.begin(),originalData.end(),[](const XMLTag tag){return tag.tag=="transformations";});
|
|
if(transformationsTag!=originalData.end())file<<(*transformationsTag).OutputTag("/>")<<std::endl;
|
|
|
|
file<<std::format("<image source=\"{}\" width=\"{}\" height=\"{}\"/>",
|
|
activeSet.filename,activeSet.imagewidth,activeSet.imageheight)<<std::endl;
|
|
|
|
std::vector<std::pair<int,std::string>>tiles;
|
|
|
|
for(auto&[name,obj]:activeSet.objects){
|
|
const std::vector<std::pair<int,std::string>>objTiles{obj.OutputTag(activeSet)};
|
|
tiles.insert(tiles.end(),objTiles.begin(),objTiles.end());
|
|
}
|
|
|
|
for(const NonObject&obj:nonObjects){
|
|
tiles.push_back(obj.OutputTag());
|
|
}
|
|
|
|
std::sort(tiles.begin(),tiles.end(),[](const std::pair<int,std::string>tileData1,const std::pair<int,std::string>tileData2){
|
|
return tileData1.first<tileData2.first;
|
|
});
|
|
|
|
for(auto&[tileID,str]:tiles){
|
|
file<<str<<std::endl;
|
|
}
|
|
|
|
file<<"</tileset>"<<std::endl;
|
|
}else{
|
|
std::cout<<"WARNING! File not available for saving! Failed to save!"<<std::endl;
|
|
}
|
|
|
|
std::ofstream saveFile{activeTileset};
|
|
saveFile<<file.str()<<std::endl;
|
|
saveFile.close();
|
|
std::cout<<"Save Successful!"<<std::endl;
|
|
}
|
|
|
|
void ResetState(){
|
|
editingPoint=4;
|
|
dragging=false;
|
|
editingQuad=nullptr;
|
|
dragTranslate=false;
|
|
highlightedQuad=nullptr;
|
|
selectedObj="";
|
|
dragNewObj=false;
|
|
}
|
|
|
|
void Update(){
|
|
const Tileset&tileset=currentTileset;
|
|
|
|
if(selectedObj.length()>0){
|
|
|
|
if(GetKey(DEL).bReleased){
|
|
currentTileset.objects.erase(selectedObj);
|
|
selectedObj="";
|
|
ResetState();
|
|
SaveFile();
|
|
return;
|
|
}
|
|
|
|
const TilesetObject&obj=tileset.objects.at(selectedObj);
|
|
const bool EditingQuad=(editingPoint<4||dragging)&&editingQuad!=nullptr;
|
|
|
|
|
|
auto GetSnapPoint=[&](){
|
|
vf2d worldCoords=view.ScreenToWorld(GetMousePos());
|
|
if(GetKey(CTRL).bHeld){
|
|
return vf2d{round(worldCoords.x),round(worldCoords.y)};
|
|
}
|
|
return vf2d{round(worldCoords.x/(tileset.tilewidth/4))*tileset.tilewidth/4,round(worldCoords.y/(tileset.tileheight/4))*tileset.tileheight/4};
|
|
};
|
|
|
|
if(EditingQuad&&!dragging){
|
|
vf2d newEditPoint=GetSnapPoint();
|
|
|
|
newEditPoint.x=std::clamp(newEditPoint.x,float(obj.bounds.left().start.x),float(obj.bounds.right().start.x));
|
|
newEditPoint.y=std::clamp(newEditPoint.y,float(obj.bounds.top().start.y),float(obj.bounds.bottom().start.y));
|
|
|
|
(*editingQuad)[editingPoint]=newEditPoint;
|
|
}else
|
|
if(EditingQuad&&dragging){
|
|
vf2d cursorPos=GetSnapPoint();
|
|
vf2d initialPoint=(*editingQuad)[0];
|
|
(*editingQuad)[1]=vf2d{cursorPos.x,initialPoint.y};
|
|
(*editingQuad)[2]=GetSnapPoint();
|
|
(*editingQuad)[3]=vf2d{initialPoint.x,cursorPos.y};
|
|
}
|
|
|
|
if(dragTranslate){
|
|
vf2d translateAmt=GetSnapPoint()-originalQuad[0]-upperLeftDragOffset;
|
|
for(size_t pointInd=0;vf2d&point:*editingQuad){
|
|
(*editingQuad)[pointInd]=originalQuad[pointInd]+translateAmt;
|
|
pointInd++;
|
|
}
|
|
}
|
|
if(editButton->bChecked){
|
|
if(GetMouse(Mouse::LEFT).bPressed){
|
|
if(highlightedQuad!=nullptr){
|
|
#pragma region Select a point on a collision quad.
|
|
for(size_t pointInd=0;const vf2d&point:*highlightedQuad){
|
|
if(geom2d::line<float>(point,view.ScreenToWorld(GetMousePos())).length()<4/view.GetWorldScale().x){
|
|
editingPoint=pointInd;
|
|
editingQuad=highlightedQuad;
|
|
originalQuad=*highlightedQuad;
|
|
goto exitCollisionCheck;
|
|
}
|
|
pointInd++;
|
|
}
|
|
#pragma endregion
|
|
|
|
dragTranslate=true;
|
|
editingQuad=highlightedQuad;
|
|
upperLeftDragOffset=GetSnapPoint()-(*highlightedQuad)[0];
|
|
originalQuad=*editingQuad;
|
|
goto exitCollisionCheck;
|
|
}
|
|
exitCollisionCheck:
|
|
if(EditingQuad&&!dragging){
|
|
vf2d newEditPoint=GetSnapPoint();
|
|
|
|
newEditPoint.x=std::clamp(newEditPoint.x,float(obj.bounds.left().start.x),float(obj.bounds.right().start.x));
|
|
newEditPoint.y=std::clamp(newEditPoint.y,float(obj.bounds.top().start.y),float(obj.bounds.bottom().start.y));
|
|
|
|
(*editingQuad)[editingPoint]=newEditPoint;
|
|
editingPoint++;
|
|
}
|
|
}
|
|
|
|
if(GetMouse(Mouse::RIGHT).bPressed||GetKey(ESCAPE).bReleased){
|
|
if(EditingQuad||dragTranslate){
|
|
ResetState();
|
|
}
|
|
}
|
|
}else{
|
|
if(GetMouse(Mouse::LEFT).bPressed){
|
|
Quadrilateral newQuad{GetSnapPoint()};
|
|
currentTileset.objects[selectedObj].collisionTiles.push_back(newQuad);
|
|
dragging=true;
|
|
editingQuad=const_cast<Quadrilateral*>(&obj.collisionTiles.back());
|
|
originalQuad=*editingQuad;
|
|
}
|
|
}
|
|
|
|
if(GetMouse(Mouse::RIGHT).bPressed&&!EditingQuad&&!dragTranslate){
|
|
if(highlightedQuad!=nullptr){
|
|
std::erase_if(currentTileset.objects[selectedObj].collisionTiles,[&](Quadrilateral&q){return &q==highlightedQuad;});
|
|
ResetState();
|
|
SaveFile();
|
|
}
|
|
}
|
|
|
|
if(GetMouse(Mouse::LEFT).bReleased){
|
|
if(EditingQuad&&dragging){
|
|
vf2d cursorPos=GetSnapPoint();
|
|
vf2d initialPoint=(*editingQuad)[0];
|
|
(*editingQuad)[1]=vf2d{cursorPos.x,initialPoint.y};
|
|
(*editingQuad)[2]=GetSnapPoint();
|
|
(*editingQuad)[3]=vf2d{initialPoint.x,cursorPos.y};
|
|
ResetState();
|
|
SaveFile();
|
|
}else
|
|
if(EditingQuad&&!dragging){
|
|
vf2d newEditPoint=GetSnapPoint();
|
|
|
|
newEditPoint.x=std::clamp(newEditPoint.x,float(obj.bounds.left().start.x),float(obj.bounds.right().start.x));
|
|
newEditPoint.y=std::clamp(newEditPoint.y,float(obj.bounds.top().start.y),float(obj.bounds.bottom().start.y));
|
|
|
|
(*editingQuad)[editingPoint]=newEditPoint;
|
|
editingPoint=4;
|
|
dragging=false;
|
|
editingQuad=nullptr;
|
|
dragTranslate=false;
|
|
highlightedQuad=nullptr;
|
|
SaveFile();
|
|
}else
|
|
if(dragTranslate){
|
|
SaveFile();
|
|
}
|
|
dragTranslate=false;
|
|
editingQuad=nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NewObjectUpdate(){
|
|
const Tileset&tileset=currentTileset;
|
|
if(GetMouse(Mouse::LEFT).bReleased){
|
|
dragNewObj=false;
|
|
|
|
vf2d worldCoords=view.ScreenToWorld(GetMousePos());
|
|
vi2d newUpperLeftTile=upperLeftObjTile;
|
|
vi2d newLowerRightTile=lowerRightObjTile;
|
|
|
|
if(worldCoords.x<upperLeftObjTile.x){
|
|
newLowerRightTile.x=int(floor(worldCoords.x/tileset.tilewidth)*tileset.tilewidth);
|
|
std::swap(newUpperLeftTile.x,newLowerRightTile.x);
|
|
}
|
|
if(worldCoords.y<upperLeftObjTile.y){
|
|
newLowerRightTile.y=int(floor(worldCoords.y/tileset.tileheight)*tileset.tileheight);
|
|
std::swap(newUpperLeftTile.y,newLowerRightTile.y);
|
|
}
|
|
|
|
if(newLowerRightTile.x==newUpperLeftTile.x)newLowerRightTile.x+=tileset.tilewidth;
|
|
if(newLowerRightTile.y==newUpperLeftTile.y)newLowerRightTile.y+=tileset.tileheight;
|
|
|
|
geom2d::rect<int>newObjRect{newUpperLeftTile,newLowerRightTile-newUpperLeftTile};
|
|
|
|
const Tileset&tileset=currentTileset;
|
|
//Check for intersection with other objects, if found then we deny creating this object this way.
|
|
bool intersectionFound=false;
|
|
for(auto&[name,obj]:tileset.objects){
|
|
geom2d::rect<float>offsetBounds{obj.bounds.pos+vf2d{0.5f,0.5f},obj.bounds.size-vf2d{1.f,1.f}};
|
|
if(geom2d::overlaps(offsetBounds,newObjRect)){
|
|
intersectionFound=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!intersectionFound){
|
|
std::string objName=std::format("Object{}",currentTileset.objects.size());
|
|
|
|
TilesetObject&newObj=currentTileset.objects[objName];
|
|
newObj.name=objName;
|
|
for(int y=0;y<newObjRect.size.y/tileset.tileheight;y++){
|
|
for(int x=0;x<newObjRect.size.x/tileset.tilewidth;x++){
|
|
int tileX=newUpperLeftTile.x/tileset.tilewidth+x;
|
|
int tileY=newUpperLeftTile.y/tileset.tileheight+y;
|
|
int tileID=tileY*tileset.columns+tileX;
|
|
newObj.AddTile(tileset,tileID);
|
|
}
|
|
}
|
|
SaveFile();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnTextEntryComplete(const std::string&sText)override{
|
|
currentTileset.objects[nameEditObj].name=sText;
|
|
SaveFile();
|
|
}
|
|
|
|
void EditorUpdate(){
|
|
view.HandlePanAndZoom();
|
|
|
|
const Tileset&tileset=currentTileset;
|
|
|
|
const float CAMERA_MOVESPD = 150.f;
|
|
|
|
if(!nameBox->m_bTextEdit){
|
|
if(selectedObj.length()>0&&GetKey(R).bReleased){
|
|
TextEntryEnable(true, nameBox->sText);
|
|
nameBox->m_bTextEdit=true;
|
|
nameEditObj=currentTileset.objects[selectedObj].name;
|
|
}
|
|
if(GetKey(W).bHeld)view.MoveWorldOffset(vf2d{0.f,-CAMERA_MOVESPD}*GetElapsedTime()/view.GetWorldScale());
|
|
if(GetKey(S).bHeld)view.MoveWorldOffset(vf2d{0.f,CAMERA_MOVESPD}*GetElapsedTime()/view.GetWorldScale());
|
|
if(GetKey(A).bHeld)view.MoveWorldOffset(vf2d{-CAMERA_MOVESPD,0.f}*GetElapsedTime()/view.GetWorldScale());
|
|
if(GetKey(D).bHeld)view.MoveWorldOffset(vf2d{CAMERA_MOVESPD,0.f}*GetElapsedTime()/view.GetWorldScale());
|
|
if(((undoButton->bPressed||GetKey(CTRL).bHeld&&GetKey(Z).bReleased))&&
|
|
undoList.size()>0){
|
|
redoList.push_back(currentTileset.objects);
|
|
currentTileset.objects.clear();
|
|
currentTileset.objects=undoList.back();
|
|
undoList.pop_back();
|
|
previousObjState=currentTileset.objects;
|
|
std::cout<<"Undo List Size:"<<undoList.size()<<std::endl;
|
|
SaveFile(true);
|
|
ResetState();
|
|
gui.Update(this);
|
|
return; //Don't process the rest of this function because we clicked and so we skip input.
|
|
}
|
|
if(((redoButton->bPressed||GetKey(CTRL).bHeld&&GetKey(Y).bReleased||GetKey(CTRL).bHeld&&GetKey(SHIFT).bHeld&&GetKey(Z).bReleased))&&
|
|
redoList.size()>0){
|
|
undoList.push_back(currentTileset.objects);
|
|
currentTileset.objects.clear();
|
|
currentTileset.objects=redoList.back();
|
|
previousObjState=currentTileset.objects;
|
|
redoList.pop_back();
|
|
std::cout<<"Redo List Size:"<<redoList.size()<<std::endl;
|
|
SaveFile(true);
|
|
ResetState();
|
|
gui.Update(this);
|
|
return;
|
|
}
|
|
if(openButton->bReleased){
|
|
selectingFile=true;
|
|
openButton->Reset();
|
|
gui.Update(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const bool EditingQuad=(editingPoint<4||dragging)&&editingQuad!=nullptr;
|
|
|
|
if(editingQuad==nullptr&&!EditingQuad){
|
|
selectedObj="";
|
|
for(auto&[objName,obj]:tileset.objects){
|
|
if(geom2d::contains(obj.bounds,view.ScreenToWorld(GetMousePos()))){
|
|
selectedObj=objName;
|
|
nameBox->sText=obj.name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(createNewButton->bPressed)editButton->bChecked=false;
|
|
if(editButton->bPressed)createNewButton->bChecked=false;
|
|
undoButton->Enable(undoList.size()>0);
|
|
redoButton->Enable(redoList.size()>0);
|
|
|
|
gui.Update(this);
|
|
|
|
if((GetMouseY()<ScreenHeight()-36||GetMouseX()>204)&&
|
|
(GetMouseX()<ScreenWidth()-72||GetMouseY()<ScreenHeight()-36)&&
|
|
(GetMouseX()<ScreenWidth()-32||GetMouseY()>ScreenHeight()+12)&&
|
|
!nameBox->m_bTextEdit&&
|
|
tileset.columns>0&&
|
|
view.ScreenToWorld(GetMousePos()).x<tileset.columns*tileset.tilewidth&&
|
|
view.ScreenToWorld(GetMousePos()).y<tileset.tilecount/tileset.columns*tileset.tileheight&&
|
|
view.ScreenToWorld(GetMousePos()).x>=0&&view.ScreenToWorld(GetMousePos()).y>=0){
|
|
if(selectedObj.length()==0){
|
|
if(GetMouse(Mouse::LEFT).bPressed){
|
|
vf2d worldCoords=view.ScreenToWorld(GetMousePos());
|
|
upperLeftObjTile=vi2d{int(floor(worldCoords.x/tileset.tilewidth)*tileset.tilewidth),int(floor(worldCoords.y/tileset.tileheight)*tileset.tileheight)};
|
|
dragNewObj=true;
|
|
}
|
|
}
|
|
if(!dragNewObj){
|
|
Update();
|
|
}else{
|
|
NewObjectUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SelectingFileUpdate(){
|
|
|
|
selectionGui.Update(this);
|
|
|
|
if(tilesetList.size()>0&&!loadedFirstFile){
|
|
loadedFirstFile=true;
|
|
tilesetsList->nSelectedItem=0;
|
|
goto loadFile;
|
|
}
|
|
|
|
if(tilesetsList->bSelectionChanged){
|
|
loadFile:
|
|
const std::string tilesetFilename{tilesetList[tilesetsList->nSelectedItem]};
|
|
parsedMap={tilesetFilename};
|
|
mapImage.Load(TILESET_DIR+parsedMap.GetData().filename);
|
|
currentTileset=parsedMap.GetData();
|
|
activeTileset=tilesetFilename;
|
|
undoList.clear();
|
|
redoList.clear();
|
|
previousObjState=currentTileset.objects;
|
|
ResetState();
|
|
}
|
|
|
|
if(GetKey(ESCAPE).bReleased){
|
|
selectingFile=false;
|
|
openButton->Reset();
|
|
gui.Update(this);
|
|
}
|
|
|
|
if(closeButton->bReleased){
|
|
closeButton->Reset();
|
|
selectingFile=false;
|
|
openButton->Reset();
|
|
gui.Update(this);
|
|
}
|
|
|
|
GradientFillRectDecal({0.f,0.f},GetScreenSize()/2.f,BLACK,BLACK,{0,0,0,0},BLACK);
|
|
GradientFillRectDecal({0.f,ScreenHeight()/2.f},GetScreenSize()/2.f,BLACK,BLACK,BLACK,{0,0,0,0});
|
|
GradientFillRectDecal(GetScreenSize()/2.f,GetScreenSize()/2.f,{0,0,0,0},BLACK,BLACK,BLACK);
|
|
GradientFillRectDecal({ScreenWidth()/2.f,0.f},GetScreenSize()/2.f,BLACK,{0,0,0,0},BLACK,BLACK);
|
|
|
|
selectionGui.DrawDecal(this);
|
|
|
|
}
|
|
|
|
void RenderTileset(){
|
|
const Tileset&tileset=currentTileset;
|
|
|
|
if(mapImage.Decal()!=nullptr){
|
|
view.DrawDecal({0,0},mapImage.Decal());
|
|
}
|
|
|
|
if(tileset.columns>0){
|
|
if(selectedObj.length()>0){
|
|
for(int y=0;y<tileset.tilecount/tileset.columns;y++){
|
|
for(int x=0;x<tileset.columns;x++){
|
|
if(!geom2d::contains(tileset.objects.at(selectedObj).bounds,vf2d{float(x*tileset.tilewidth)+tileset.tilewidth/2,float(y*tileset.tileheight)+tileset.tileheight/2})){
|
|
view.FillRectDecal(vf2d{float(x),float(y)}*tileset.tilewidth,vf2d{float(tileset.tilewidth),float(tileset.tileheight)},{0,0,0,128});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(int y=0;y<tileset.tilecount/tileset.columns;y++){
|
|
for(int x=0;x<tileset.columns;x++){
|
|
view.DrawRectDecal(vf2d{float(x),float(y)}*tileset.tilewidth,vf2d{float(tileset.tilewidth),float(tileset.tileheight)},GREY);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(selectedObj.length()>0&&!dragNewObj){
|
|
const TilesetObject&obj=tileset.objects.at(selectedObj);
|
|
|
|
if(highlightedQuad!=nullptr){
|
|
std::array<geom2d::triangle<float>,2>collisionTris{
|
|
geom2d::triangle<float>{(*highlightedQuad)[0],(*highlightedQuad)[1],(*highlightedQuad)[2]},
|
|
geom2d::triangle<float>{(*highlightedQuad)[0],(*highlightedQuad)[2],(*highlightedQuad)[3]},
|
|
};
|
|
for(geom2d::triangle<float>&tri:collisionTris){
|
|
if(geom2d::overlaps(tri,geom2d::circle<float>(view.ScreenToWorld(GetMousePos()),3.f))){
|
|
goto renderQuads;
|
|
}
|
|
}
|
|
highlightedQuad=nullptr;
|
|
}else{
|
|
for(const Quadrilateral&quad:obj.collisionTiles){
|
|
std::array<geom2d::triangle<float>,2>collisionTris{
|
|
geom2d::triangle<float>{quad[0],quad[1],quad[2]},
|
|
geom2d::triangle<float>{quad[0],quad[2],quad[3]},
|
|
};
|
|
for(geom2d::triangle<float>&tri:collisionTris){
|
|
if(geom2d::overlaps(tri,geom2d::circle<float>(view.ScreenToWorld(GetMousePos()),3.f))){
|
|
highlightedQuad=const_cast<Quadrilateral*>(&quad);
|
|
goto renderQuads;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
renderQuads:
|
|
for(const Quadrilateral&quad:obj.collisionTiles){
|
|
std::vector<vf2d>points;
|
|
std::vector<vf2d>uvs;
|
|
std::vector<Pixel>cols;
|
|
points.assign(quad.begin(),quad.end());
|
|
uvs.assign(4,{0.f,0.f});
|
|
cols.assign(4,(!GetMouse(Mouse::LEFT).bHeld&&highlightedQuad==&quad)?Pixel{255,20,20,150}:Pixel{255,40,40,128});
|
|
view.DrawPolygonDecal(nullptr,points,uvs,cols);
|
|
|
|
for(bool highlighted=false;const vf2d&point:quad){
|
|
if(highlightedQuad==&quad){
|
|
if(geom2d::line<float>(point,view.ScreenToWorld(GetMousePos())).length()<4/view.GetWorldScale().x&&!highlighted){
|
|
view.DrawRotatedDecal(point,circle.Decal(),0.f,circle.Sprite()->Size()/2,vf2d{1.f,1.f}/view.GetWorldScale(),YELLOW);
|
|
highlighted=true;
|
|
}else{
|
|
view.DrawRotatedDecal(point,circle.Decal(),0.f,circle.Sprite()->Size()/2,vf2d{1.f,1.f}/view.GetWorldScale(),RED);
|
|
}
|
|
}else{
|
|
view.DrawRotatedDecal(point,circle.Decal(),0.f,circle.Sprite()->Size()/2,vf2d{0.25f,0.25f}/view.GetWorldScale(),DARK_GREY);
|
|
}
|
|
}
|
|
}
|
|
}else
|
|
if(dragNewObj){
|
|
vf2d worldCoords=view.ScreenToWorld(GetMousePos());
|
|
lowerRightObjTile=vi2d{int(ceil(worldCoords.x/tileset.tilewidth)*tileset.tilewidth),int(ceil(worldCoords.y/tileset.tileheight)*tileset.tileheight)};
|
|
|
|
vi2d newUpperLeftTile=upperLeftObjTile;
|
|
vi2d newLowerRightTile=lowerRightObjTile;
|
|
|
|
if(worldCoords.x<upperLeftObjTile.x){
|
|
newLowerRightTile.x=int(floor(worldCoords.x/tileset.tilewidth)*tileset.tilewidth);
|
|
std::swap(newUpperLeftTile.x,newLowerRightTile.x);
|
|
}
|
|
if(worldCoords.y<upperLeftObjTile.y){
|
|
newLowerRightTile.y=int(floor(worldCoords.y/tileset.tileheight)*tileset.tileheight);
|
|
std::swap(newUpperLeftTile.y,newLowerRightTile.y);
|
|
}
|
|
|
|
if(newLowerRightTile.x==newUpperLeftTile.x)newLowerRightTile.x+=tileset.tilewidth;
|
|
if(newLowerRightTile.y==newUpperLeftTile.y)newLowerRightTile.y+=tileset.tileheight;
|
|
|
|
Pixel overlayCol={0,0,255,160};
|
|
|
|
geom2d::rect<int>newObjRect{newUpperLeftTile,newLowerRightTile-newUpperLeftTile};
|
|
|
|
for(auto&[name,obj]:tileset.objects){
|
|
geom2d::rect<float>offsetBounds{obj.bounds.pos+vf2d{0.5f,0.5f},obj.bounds.size-vf2d{1.f,1.f}};
|
|
if(geom2d::overlaps(offsetBounds,newObjRect)){
|
|
overlayCol={255,0,0,160};
|
|
break;
|
|
}
|
|
}
|
|
|
|
view.FillRectDecal(newObjRect.pos,newObjRect.size,overlayCol);
|
|
}
|
|
|
|
for(auto&[objName,obj]:tileset.objects){
|
|
view.DrawLineDecal(obj.bounds.pos,obj.bounds.pos+vf2d{0.f,float(obj.bounds.size.y)},YELLOW);
|
|
view.DrawLineDecal(obj.bounds.pos,obj.bounds.pos+vf2d{float(obj.bounds.size.x),0.f},YELLOW);
|
|
view.DrawLineDecal(obj.bounds.pos+obj.bounds.size,obj.bounds.pos+obj.bounds.size+vf2d{0.f,-float(obj.bounds.size.y)},YELLOW);
|
|
view.DrawLineDecal(obj.bounds.pos+obj.bounds.size,obj.bounds.pos+obj.bounds.size+vf2d{-float(obj.bounds.size.x),0.f},YELLOW);
|
|
|
|
vi2d nameTextSize=GetTextSizeProp(obj.name)*0.25f;
|
|
if(!geom2d::overlaps(geom2d::rect<float>{obj.bounds.pos,nameTextSize+vf2d{2,2}},view.ScreenToWorld(GetMousePos()))){
|
|
view.GradientFillRectDecal(obj.bounds.pos,nameTextSize+vf2d{2,2},RED,{255,0,0,64},{255,0,0,64},RED);
|
|
view.DrawStringPropDecal(obj.bounds.pos+vf2d{1.25f,1.25f},obj.name,BLACK,vf2d{0.25f,0.25f});
|
|
view.DrawStringPropDecal(obj.bounds.pos+vf2d{1,1},obj.name,WHITE,vf2d{0.25f,0.25f});
|
|
}
|
|
}
|
|
|
|
gui.DrawDecal(this);
|
|
|
|
if(!nameBox->m_bTextEdit){
|
|
createNewButton->Enable(true);
|
|
editButton->Enable(true);
|
|
FillRectDecal(nameBox->vPos,nameBox->vSize,{0,0,0,150});
|
|
if(selectedObj.length()>0){
|
|
DrawStringPropDecal(nameBox->vPos+vf2d{2.f,0.f},"R to Edit",WHITE,{0.6f,0.6f});
|
|
}
|
|
}else{
|
|
createNewButton->Enable(false);
|
|
editButton->Enable(false);
|
|
}
|
|
|
|
DrawStringDecal(createNewButton->vPos+vf2d{3,0},"Q");
|
|
DrawStringDecal(editButton->vPos+vf2d{3,0},"E");
|
|
}
|
|
|
|
bool OnUserUpdate(float fElapsedTime) override
|
|
{
|
|
Clear(VERY_DARK_BLUE);
|
|
const bool editingFile=!selectingFile;
|
|
|
|
if(IsFocused()&&editingFile){
|
|
EditorUpdate();
|
|
}
|
|
|
|
RenderTileset();
|
|
|
|
if(IsFocused()&&selectingFile){
|
|
SelectingFileUpdate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
int main()
|
|
{
|
|
TiledCollisionEditor demo;
|
|
if (demo.Construct(640, 180, 4, 4, false, true))
|
|
demo.Start();
|
|
|
|
return 0;
|
|
}
|
|
|