public override bool DrawToAtlas(TextureAtlas atlas,AtlasLocation location){ // Only ever called with a static image: Color32[] pixelBlock=Image.GetPixels32(); int index=0; int atlasIndex=location.BottomLeftPixel(); // How many pixels must we add on to the end of the row to get to // the start of the row above? This is RowPixelDelta: int rowDelta=location.RowPixelDelta(); int height=Image.height; int width=Image.width; for(int h=0;h<height;h++){ for(int w=0;w<width;w++){ atlas.Pixels[atlasIndex++]=pixelBlock[index++]; } atlasIndex+=rowDelta; } return true; }
/// <summary>Called when this character goes on screen.</summary> public void OffScreen(){ if(Location==null){ return; } if(Location.DecreaseUsage()){ Location=null; } }
/// <summary>Called when this character goes on screen.</summary> public void OnScreen(){ if(Location==null){ Location=AtlasStacks.Text.RequireImage(this); } if(Location!=null){ Location.UsageCount++; } }
/// <summary>Clears all content from this atlas</summary> internal void Reset() { FirstEmpty = LastEmpty = null; ColumnProgressX = 0; ColumnProgressY = 0; ColumnWidth = 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>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(); }
/// <summary>Adds this location to the set of empty locations on the atlas. /// This allows other textures to use this space on the atlas.</summary> public void AddToEmptySet() { if (Empty) { return; } Empty = true; UsageCount = 0; // Make sure nothing follows/preceeds this: EmptyBefore = EmptyAfter = null; if (Spacing != 0) { Width += Spacing; Height += Spacing; Spacing = 0; } if (Atlas.FirstEmpty == null) { Atlas.FirstEmpty = Atlas.LastEmpty = this; } else { // If we have no texture, this is an empty block. // Empty blocks go to the start, deallocated texture blocks go to the end. // This allows for a potential preference on empty blocks, and the fast // restoration of a texture if it e.g. went temporarily off screen. // I.e. by putting them at the end, they have the highest chance of survival. // Chances are the newest ones are also the smallest too. if (Image == null) { // Push to start of empty queue: EmptyAfter = Atlas.FirstEmpty; Atlas.FirstEmpty = Atlas.FirstEmpty.EmptyBefore = this; } else { // Push to end of empty queue: EmptyBefore = Atlas.LastEmpty; Atlas.LastEmpty = Atlas.LastEmpty.EmptyAfter = this; } } }
/// <summary>Attempts to add the "remote" atlas location to this atlas. /// The original location object is retained.</summary> /// <returns>True if this atlas accepted the location.</summary> internal bool OptimiseAdd(AtlasLocation location) { int fitFactor = 0; int area = location.Area; AtlasLocation currentAccepted = null; AtlasLocation currentEmpty = FirstEmpty; while (currentEmpty != null) { int factor = currentEmpty.FitFactor(location.Width, location.Height, 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) { return(false); } // We've got a block of space that we'll be "adding" to. // Note that we're going to *keep* the original location. currentAccepted.OptimiseSelect(location); return(true); }
/// <summary>Optimizes the atlas by removing all 'holes' (removed images) from the atlas. /// It reconstructs the whole atlas (only when there are actually holes), so this method should be considered expensive. /// This is only ever called when we fail to add something to the atlas; Theres no performace issues of a non-optimized atlas. /// Instead it just simply has very fragmented space available.</summary> public bool Optimize() { if (!CanOptimize) { // It'll do as it is. return(false); } // Make sure it's not called again: CanOptimize = false; OptimizeRequested = false; Dictionary <int, AtlasLocation> allImages = Stack.ActiveImages; // Clear the textures and add in the starting empty location. Reset(); // Next up, add them all back in, and that's it! // The optimizing comes from them trying to fit in the smallest possible gap they can when added. foreach (KeyValuePair <int, AtlasLocation> kvp in allImages) { AtlasLocation location = kvp.Value; if (location.Atlas == this) { AtlasEntity image = location.Image; int entityID = image.GetAtlasID(); int width; int height; image.GetDimensionsOnAtlas(out width, out height); Add(image, entityID, width, height); } } return(true); }
/// <summary>Requests to draw the given path at the given atlas location.</summary> public static void RequestDraw(AtlasLocation location, VectorPath path, float offsetX, float offsetY, float drawHeight) { DrawingTexture drawing = new DrawingTexture(); drawing.Location = location; drawing.Path = path; drawing.OffsetX = offsetX; drawing.OffsetY = offsetY; if (Camera == null) { Camera = new TextureCamera(CPUCopyMode); // Apply scale: Scale = drawHeight * Camera.WorldPerPixel.x; } if (Camera.IsDrawing || !Camera.TryFit(drawing)) { // Add to global pending queue: drawing.NextDrawing = Pending; Pending = drawing; } }
/// <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>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(); }
protected override bool NowOnScreen(){ if(Image==null || Image.Image==null){ // Reject the visibility state change. ImageLocation=null; return false; } ImageLocation=RequireImage(Image); return true; }
/// <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>Draws this image to the given atlas.</summary> public virtual bool DrawToAtlas(TextureAtlas atlas,AtlasLocation location){ return false; }
/// <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>Attempts to add the "remote" atlas location to this atlas. /// The original location object is retained.</summary> /// <returns>True if this atlas accepted the location.</summary> internal bool OptimiseAdd(AtlasLocation location){ int fitFactor=0; int area=location.Area; AtlasLocation currentAccepted=null; AtlasLocation currentEmpty=FirstEmpty; while(currentEmpty!=null){ int factor=currentEmpty.FitFactor(location.Width,location.Height,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){ return false; } // We've got a block of space that we'll be "adding" to. // Note that we're going to *keep* the original location. currentAccepted.OptimiseSelect(location); return true; }
/// <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>Optimises the frames on this stack if it's needed.</summary> public bool OptimiseIfNeeded() { if (!OptimizeRequested) { return(false); } OptimizeRequested = false; TextureAtlas requiresOptimise = null; TextureAtlas current = First; while (current != null) { if (current.OptimizeRequested) { // Pop it out: current.RemoveFromStack(); // And add it to our temp stack: current.Next = requiresOptimise; requiresOptimise = current; } current = current.Next; } if (requiresOptimise == null) { return(false); } Dictionary <int, AtlasLocation> allImages = ActiveImages; // Next, for each one.. current = requiresOptimise; while (current != null) { // Grab the next one: TextureAtlas next = current.Next; // Offload its images into the "remaining" stack. Note that we must retain the actual Location objects so we don't have to re-request/ re-generate all of them. // If none fit, we re-add current as the new top of stack and add the images back onto it. // Have we re-added current? bool added = false; // Next up, add them all back in, and that's it! // The optimizing comes from them trying to fit in the smallest possible gap they can when added. foreach (KeyValuePair <int, AtlasLocation> kvp in allImages) { AtlasLocation location = kvp.Value; if (location.Atlas == current) { // Try adding to the stack: TextureAtlas stackAtlas = Last; bool noAdd = true; while (stackAtlas != null) { // Try adding to the current frame: if (stackAtlas.OptimiseAdd(location)) { noAdd = false; break; } stackAtlas = stackAtlas.Previous; } if (noAdd && !added) { added = true; // Didn't fit in any of them! We now clear out the current atlas and re-add it like so: Add(current); // Ensure it's cleared: current.Reset(); // Add to it instead: current.OptimiseAdd(location); } } } if (!added) { // Destroy it: current.Destroy(); } current = next; } return(true); }
/// <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(); }
public bool DrawToAtlas(TextureAtlas atlas,AtlasLocation location){ // Only ever called with a static image: Color32[] pixelBlock=Image.GetPixels32(); int index=0; int atlasIndex=location.BottomLeftPixel(); int height=Image.height; int width=Image.width; // How many pixels must we add on to the end of the row to get to // the start of the row above? This is simply the dimension of the atlas: int rowDelta=atlas.Dimension; for(int h=0;h<height;h++){ Array.Copy(pixelBlock,index,atlas.Pixels,atlasIndex,width); index+=width; atlasIndex+=rowDelta; } return true; }
/// <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(); }
protected override void NowOffScreen(){ if(ImageLocation==null){ return; } if(ImageLocation.DecreaseUsage()){ ImageLocation=null; } }
/// <summary>Sets the vertices of this box to that specified by the given block /// but clipped to fit within a boundary. At the same time, an image is applied /// to the block and its UV coordinates are also clipped.</summary> /// <param name="boundary">The clipping boundary. The vertices will be clipped to within this.</param> /// <param name="block">The position of the vertices.</param> /// <param name="renderer">The renderer that will render this block.</param> /// <param name="zIndex">The depth of the vertices.</param> /// <param name="imgLocation">The location of the image on the meshes atlas.</param> public UVBlock SetClipped(BoxRegion boundary,BoxRegion block,Renderman renderer,float zIndex,AtlasLocation imgLocation,UVBlock uvBlock){ // Image defines how big we want the image to be in pixels on the screen. // So firstly we need to find the ratio of how scaled our image actually is: float originalHeight=block.Height; float scaleX=imgLocation.Width/block.Width; float scaleY=imgLocation.Height/originalHeight; // We'll need to clip block and make sure the image block is clipped too: float blockX=block.X; float blockY=block.Y; if(block.ClipByChecked(boundary)){ // It actually got clipped - time to do some UV clipping too. // Apply the verts: ApplyVertices(block,renderer,zIndex); block.X-=blockX; block.Y-=blockY; block.MaxX-=blockX; block.MaxY-=blockY; // Flip the gaps (the clipped and now 'missing' sections) - UV's are inverted relative to the vertices. // Bottom gap is just block.Y: float bottomGap=block.Y; // Top gap is the original height - the new maximum; write it to the bottom gap: block.Y=originalHeight-block.MaxY; // Update the top gap: block.MaxY=originalHeight-bottomGap; // Image was in terms of real screen pixels, so now we need to scale it to being in 'actual image' pixels. // From there, the region block.X*=scaleX; block.MaxX*=scaleX; block.Y*=scaleY; block.MaxY*=scaleY; if(uvBlock==null || uvBlock.Shared){ // Create the UV block: uvBlock=new UVBlock(); } // Get the new max/min values: uvBlock.MinX=imgLocation.GetU(block.X+0.2f); uvBlock.MaxX=imgLocation.GetU(block.MaxX-0.2f); uvBlock.MaxY=imgLocation.GetV(block.MaxY-0.2f); uvBlock.MinY=imgLocation.GetV(block.Y+0.2f); }else{ // Apply the verts: ApplyVertices(block,renderer,zIndex); // Globally share the UV! uvBlock=imgLocation; } return uvBlock; }
protected override void Layout(){ if(Image==null || !Image.Loaded()){ return; } if(Clipping==BackgroundClipping.Text){ return; } Renderman renderer=Element.Document.Renderer; if(Image.Animated || Image.IsDynamic || renderer.RenderMode==RenderMode.NoAtlas || Filtering!=FilterMode.Point || ForcedIsolate){ // SPA is an animation format, so we need a custom texture atlas to deal with it. // This is because the frames of any animation would quickly exhaust our global texture atlas. // So to get a custom atlas, we must isolate this property. Isolate(); }else if(Image.IsVideo){ // Similarly with a video, we need to isolate it aswell. Isolate(); #if !MOBILE if(!Image.Video.isPlaying && Element["autoplay"]!=null){ // Play now: Image.Video.Play(); // Fire an onplay event: Element.Run("onplay"); // Clear: Element["autoplay"]=null; } #endif }else{ // Reverse isolation, if we are isolated already: Include(); } ComputedStyle computed=Element.Style.Computed; // Get the full shape of the element: int width=computed.PaddedWidth; int height=computed.PaddedHeight; int minY=computed.OffsetTop+computed.BorderTop; int minX=computed.OffsetLeft+computed.BorderLeft; if(width==0||height==0){ if(Visible){ SetVisibility(false); } return; } BoxRegion boundary=new BoxRegion(minX,minY,width,height); if(!boundary.Overlaps(renderer.ClippingBoundary)){ if(Visible){ SetVisibility(false); } return; }else if(!Visible){ // ImageLocation will allocate here if it's needed. SetVisibility(true); } boundary.ClipBy(renderer.ClippingBoundary); // Texture time - get it's location on that atlas: AtlasLocation locatedAt=ImageLocation; if(locatedAt==null){ // We're not using the atlas here. if(!Isolated){ Isolate(); } int imgWidth=Image.Width(); int imgHeight=Image.Height(); locatedAt=new AtlasLocation(0,0,imgWidth,imgHeight,imgWidth,imgHeight); } // Isolation is all done - safe to setup the batch now: SetupBatch(locatedAt.Atlas,null); // Great - Use locatedAt.Width/locatedAt.Height - this removes any risk of overflowing into some other image. int imageCountX=1; int imageCountY=1; int trueImageWidth=locatedAt.Width; int trueImageHeight=locatedAt.Height; int imageWidth=trueImageWidth; int imageHeight=trueImageHeight; bool autoX=false; bool autoY=false; if(Image.PixelPerfect){ imageWidth=(int)(imageWidth*ScreenInfo.ResolutionScale); imageHeight=(int)(imageWidth*ScreenInfo.ResolutionScale); } if(SizeX!=null){ if(SizeX.Single!=0f){ imageWidth=(int)(width*SizeX.Single); }else if(SizeX.PX!=0){ imageWidth=SizeX.PX; }else if(SizeX.IsAuto()){ autoX=true; } } if(SizeY!=null){ if(SizeY.Single!=0f){ imageHeight=(int)(height*SizeY.Single); }else if(SizeY.PX!=0){ imageHeight=SizeY.PX; }else if(SizeY.IsAuto()){ autoY=true; } } if(autoX){ imageWidth=imageHeight * trueImageWidth / trueImageHeight; }else if(autoY){ imageHeight=imageWidth * trueImageHeight / trueImageWidth; } // offsetX and offsetY are the images position offset from where it should be (e.g. x of -200 means it's 200px left) // Resolve the true offset values: int offsetX=0; int offsetY=0; if(OffsetX!=null){ // Resolve a potential mixed % and px: offsetX=OffsetX.GetMixed(width-imageWidth); } if(OffsetY!=null){ // Resolve a potential mixed % and px: offsetY=OffsetY.GetMixed(height-imageHeight); } if(RepeatX){ // Get the rounded up number of images: imageCountX=(width-1)/imageWidth+1; if(offsetX!=0){ // If we have an offset, another image is introduced. imageCountX++; } } if(RepeatY){ // Get the rounded up number of images: imageCountY=(height-1)/imageHeight+1; if(offsetY!=0){ // If we have an offset, another image is introduced. imageCountY++; } } int blockX=minX+offsetX; int blockY=minY+offsetY; if(RepeatX&&offsetX>0){ // We're repeating and the image is offset by a +ve number. // This means a small gap, OffsetX px wide, is open on this left side. // So to fill it, we need to offset this first image by a much bigger number - the value imageWidth-OffsetX. blockX-=(imageWidth-offsetX); // This results in the first image having OffsetX pixels exposed in the box - this is what we want. } if(RepeatY&&offsetY>0){ // Similar thing to above: blockY-=(imageHeight-offsetY); } BoxRegion screenRegion=new BoxRegion(); bool first=true; int startX=blockX; Color colour=computed.ColorOverlay; float zIndex=(computed.ZIndex-0.003f); for(int y=0;y<imageCountY;y++){ for(int x=0;x<imageCountX;x++){ // Draw at blockX/blockY. screenRegion.Set(blockX,blockY,imageWidth,imageHeight); if(screenRegion.Overlaps(boundary)){ // If the two overlap, this means it's actually visible. MeshBlock block=Add(); if(Image.Animated&&first){ first=false; // Make sure we have an instance: Image.GoingOnDisplay(); block.ParentMesh.SetMaterial(Image.Animation.AnimatedMaterial); }else if(Image.IsVideo&&first){ first=false; block.ParentMesh.SetMaterial(Image.VideoMaterial); }else if(Isolated&&first){ first=false; block.ParentMesh.SetMaterial(Image.ImageMaterial); } // Set it's colour: block.SetColour(colour); // And clip our meshblock to fit within boundary: block.TextUV=null; block.ImageUV=block.SetClipped(boundary,screenRegion,renderer,zIndex,locatedAt,block.ImageUV); } blockX+=imageWidth; } blockX=startX; blockY+=imageHeight; } }
/// <summary>Adds this location to the set of empty locations on the atlas. /// This allows other textures to use this space on the atlas.</summary> public void AddToEmptySet(){ if(Empty){ return; } Empty=true; UsageCount=0; // Make sure nothing follows/preceeds this: EmptyBefore=EmptyAfter=null; if(Spacing!=0){ Width+=Spacing; Height+=Spacing; Spacing=0; } if(Atlas.FirstEmpty==null){ Atlas.FirstEmpty=Atlas.LastEmpty=this; }else{ // If we have no texture, this is an empty block. // Empty blocks go to the start, deallocated texture blocks go to the end. // This allows for a potential preference on empty blocks, and the fast // restoration of a texture if it e.g. went temporarily off screen. // I.e. by putting them at the end, they have the highest chance of survival. // Chances are the newest ones are also the smallest too. if(Image==null){ // Push to start of empty queue: EmptyAfter=Atlas.FirstEmpty; Atlas.FirstEmpty=Atlas.FirstEmpty.EmptyBefore=this; }else{ // Push to end of empty queue: EmptyBefore=Atlas.LastEmpty; Atlas.LastEmpty=Atlas.LastEmpty.EmptyAfter=this; } } }