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++;
			}
			
		}
Beispiel #4
0
        /// <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;
                }
            }
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #8
0
        /// <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;
            }
        }
Beispiel #10
0
        /// <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;
		}
Beispiel #16
0
        /// <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;
				}
			}
		}