/// <summary>Clears all content from this atlas</summary> internal void Reset() { FirstEmpty = LastEmpty = null; ColumnProgress = 0; // Add the root atlas location (NB: it adds itself internally) AtlasLocation root = new AtlasLocation(this, 0, 0, Dimension, Dimension); // Immediately mark this as empty: root.AddToEmptySet(); }
/// <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. AtlasLocation currentAccepted = null; int area = spacedWidth * spacedHeight; if (Mode == AtlasingMode.Columns) { // Space in this column? int max = ColumnProgressY + spacedHeight; if (max <= Dimension) { // Yep! Create a location here: currentAccepted = new AtlasLocation(this, ColumnProgressX, ColumnProgressY, spacedWidth, spacedHeight); // Move it: ColumnProgressY += spacedHeight; if (spacedWidth > ColumnWidth) { // Update width: ColumnWidth = spacedWidth; } // 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(); } else { // Space to generate a new column? max = ColumnProgressX + ColumnWidth + spacedWidth; if (max <= Dimension) { // Set Y: ColumnProgressY = spacedHeight; // Move X: ColumnProgressX += ColumnWidth; // Yep! Create a location here: currentAccepted = new AtlasLocation(this, ColumnProgressX, 0, spacedWidth, spacedHeight); // Reset width: ColumnWidth = spacedWidth; // 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(); } // Otherwise, the atlas is practically full. // We're gonna just reject the add (by falling below), and state that it can be optimised. // This triggers another atlas to get created and this one will // be optimised at some point in the near future. } } else { int fitFactor = 0; 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 (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>Used during the optimise process. This texture has selected this location to fit into. This adds it to the atlas.</summary> /// <param name="location">The new location to write this region to.</param> public void OptimiseSelect(AtlasLocation location) { // The given texture wants to go in this location. // 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 width = location.Width; int height = location.Height; Spacing = location.Spacing; int spacedWidth = width + Spacing; int spacedHeight = height + Spacing; if (Atlas.Mode == AtlasingMode.SmallestSpace) { // Create the new empty zones: if (Width > width) { // 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 > height) { // 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; } else { // 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, Atlas.ColumnProgress - maxX, 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(); } } // Update atlas: location.Atlas = Atlas; // Move location so it acts like its in the same location as this: location.X = X; location.Y = Y; // Update the UV's: location.BakeUV(); // Write it in: location.Flush(); }
/// <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>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>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>Clears all content from this atlas</summary> internal void Reset(){ FirstEmpty=LastEmpty=null; ColumnProgress=0; // Add the root atlas location (NB: it adds itself internally) AtlasLocation root=new AtlasLocation(this,0,0,Dimension,Dimension); // Immediately mark this as empty: root.AddToEmptySet(); }
/// <summary>Used during the optimise process. This texture has selected this location to fit into. This adds it to the atlas.</summary> /// <param name="location">The new location to write this region to.</param> public void OptimiseSelect(AtlasLocation location){ // The given texture wants to go in this location. // 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 width=location.Width; int height=location.Height; Spacing=location.Spacing; int spacedWidth=width+Spacing; int spacedHeight=height+Spacing; if(Atlas.Mode==AtlasingMode.SmallestSpace){ // Create the new empty zones: if(Width>width){ // 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>height){ // 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; }else{ // 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,Atlas.ColumnProgress-maxX,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(); } } // Update atlas: location.Atlas=Atlas; // Move location so it acts like its in the same location as this: location.X=X; location.Y=Y; // Update the UV's: location.BakeUV(); // Write it in: location.Flush(); }
/// <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(); }