/// <summary>Adds the given texture to the atlas if it's not already on it, /// taking up a set amount of space on the atlas.</summary> /// <param name="texture">The texture to add.</param> /// <param name="width">The x amount of space to take up on the atlas.</param> /// <param name="height">The y amount of space to take up on the atlas.</param> /// <returns>The location of the texture on the atlas.</returns> internal AtlasLocation Add(AtlasEntity texture,int entityID,int width,int height){ // Pad width/height: int spacedWidth=width+RawSpacing; int spacedHeight=height+RawSpacing; // Look for a spot to park this texture in the set of empty blocks. // The aim is to make it fit in the smallest empty block possible to save space. // This is done with a 'fitFactor' - this is simply the difference between the blocks area and the textures area. // We want this value to be as small as possible. int fitFactor=0; AtlasLocation currentAccepted=null; int area=spacedWidth*spacedHeight; AtlasLocation currentEmpty=FirstEmpty; while(currentEmpty!=null){ int factor=currentEmpty.FitFactor(spacedWidth,spacedHeight,area); if(factor==0){ // Perfect fit - break right now; can't beat that! currentAccepted=currentEmpty; break; }else if(factor!=-1){ // We can possibly fit here - is it the current smallest? if(currentAccepted==null||factor<fitFactor){ // Yep! select it. fitFactor=factor; currentAccepted=currentEmpty; } } currentEmpty=currentEmpty.EmptyAfter; } if(Mode==AtlasingMode.Columns && currentAccepted==null){ // Is there any more column space? int max=ColumnProgress + spacedWidth; if(max<=Dimension){ // Yep! Create a location using the remaining space: currentAccepted=new AtlasLocation(this,ColumnProgress,0,Dimension-ColumnProgress,Dimension); // As it's a new location, it's empty by default: // (Note that it must be in the empty set, otherwise Select will throw the empty set entirely) currentAccepted.AddToEmptySet(); } } if(currentAccepted==null){ // No space in this atlas to fit it in. Stop there. if(CanOptimize){ // Request an optimise: OptimizeRequested=true; Stack.OptimizeRequested=true; } return null; } Stack.ActiveImages[entityID]=currentAccepted; // And burn in the texture to the location (nb: it internally also writes the pixels to the atlas). currentAccepted.Select(texture,width,height,RawSpacing); return currentAccepted; }
/// <summary>This texture has selected this location to fit into. This adds it to the atlas.</summary> /// <param name="texture">The texture that wants to go here.</param> /// <param name="width">The width of the texture in pixels.</param> /// <param name="height">The height of the texture in pixels.</param> public void Select(AtlasEntity image,int width,int height,int spacing){ // The given texture wants to go in this location. // Width and height are given for dynamic textures - textures who's pixels are actually written straight to the atlas. Empty=false; int spacedWidth=width+spacing; int spacedHeight=height+spacing; // Remove from empty queue: if(EmptyBefore==null){ Atlas.FirstEmpty=EmptyAfter; }else{ EmptyBefore.EmptyAfter=EmptyAfter; } if(EmptyAfter==null){ Atlas.LastEmpty=EmptyBefore; }else{ EmptyAfter.EmptyBefore=EmptyBefore; } int entityID=image.GetAtlasID(); if(AtlasID==entityID){ // This is a restore - we don't need to do anything else. return; } Image=image; AtlasID=entityID; // If it's not a perfect fit, generate new atlas locations to the right and above of this one. if(Atlas.Mode==AtlasingMode.SmallestSpace){ if(Width>spacedWidth){ // The textures a little thin. // Generate a new area to the right of this one (NB: 0,0 is bottom left) AtlasLocation newRight=new AtlasLocation(Atlas,X+spacedWidth,Y,Width-spacedWidth,height); // Immediately mark this as empty: newRight.AddToEmptySet(); } if(Height>spacedHeight){ // The textures a little short. // Generate a new area above this one (NB: 0,0 is bottom left) AtlasLocation newTop=new AtlasLocation(Atlas,X,Y+spacedHeight,Width,Height-spacedHeight); // Immediately mark this as empty: newTop.AddToEmptySet(); } }else{ int maxX=X+spacedWidth; if(Atlas.ColumnProgress < maxX){ Atlas.ColumnProgress=maxX; } if(Height>spacedHeight){ // The textures a little short. // Generate a new area above this one (NB: 0,0 is bottom left) AtlasLocation newTop=new AtlasLocation(Atlas,X,Y+spacedHeight,Width,Height-spacedHeight); // Immediately mark this as empty: newTop.AddToEmptySet(); } } // Set the new size of this location for UV baking: Width=width; Height=height; Spacing=spacing; // Update the UV's: BakeUV(); // Make sure the area is up to date: Area=spacedWidth*spacedHeight; // Write it in: Flush(); }
/// <summary>Require the given image on any atlas. Note that this may reject the requirement if the image is too big and isn't worthwhile on an atlas.</summary> public AtlasLocation RequireImage(AtlasEntity image){ int entityID=image.GetAtlasID(); AtlasLocation result; if(ActiveImages.TryGetValue(entityID,out result)){ // Most calls fall through here. return result; } int width; int height; image.GetDimensionsOnAtlas(out width,out height); if(width>InitialSize || height>InitialSize){ // Won't fit or is unsuitable for atlasing anyway. return null; } if(Last==null){ Create(); }else{ // Fast check - was this texture recently removed from any atlas? // We might have the chance of restoring it. // Their added at the back of the empty queue, so naturally, start at the end of the empty set // and go back until we hit one with a null texture. TextureAtlas currentAtlas=Last; while(currentAtlas!=null){ AtlasLocation currentE=currentAtlas.LastEmpty; while(currentE!=null){ if(currentE.Image==null){ // Nope! Shame. break; }else if(currentE.AtlasID==entityID){ // Ace! Time to bring it back from the dead. currentE.Select(image,width,height,Spacing); ActiveImages[entityID]=currentE; return currentE; } currentE=currentE.EmptyBefore; } currentAtlas=currentAtlas.Previous; } } // Push to top of stack: result=Last.Add(image,entityID,width,height); if(result!=null){ return result; } // Non-fitter - try fitting in lower stack frames: TextureAtlas current=Last.Previous; while(current!=null){ result=current.Add(image,entityID,width,height); if(result!=null){ return result; } current=current.Previous; } // Still not fitting! Create a new stack frame: Create(); return Last.Add(image,entityID,width,height); }
//--------------------------------------
/// <summary>Removes a texture from the atlas.</summary> /// <param name="texture">The texture to remove.</param> public void Remove(AtlasEntity texture){ if(texture==null){ return; } AtlasLocation location=Get(texture.GetAtlasID()); if(location==null){ return; } // Make the location available: location.Deselect(); }