diff --git a/.gitignore b/.gitignore index afc4c9889..47343ecd9 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ FRBDK/Glue/GameScriptingPlugin/.vs/ FRBDK/AnimationEditorPlugin/.vs/ *.mainplugin.settings.json Templates/FlatRedBallUwpTemplate/.vs/ +FRBDK/GlueView2Test/packages/ +FRBDK/GlueView2Test/.vs/ +FRBDK/GlueView2/packages/ diff --git a/FRBDK/GlueView2/Dependencies/FlatRedBall.Content.dll b/FRBDK/GlueView2/Dependencies/FlatRedBall.Content.dll new file mode 100644 index 000000000..ecc1e9c82 Binary files /dev/null and b/FRBDK/GlueView2/Dependencies/FlatRedBall.Content.dll differ diff --git a/FRBDK/GlueView2/Dependencies/FlatRedBall.XML b/FRBDK/GlueView2/Dependencies/FlatRedBall.XML new file mode 100644 index 000000000..b8c9b5809 --- /dev/null +++ b/FRBDK/GlueView2/Dependencies/FlatRedBall.XML @@ -0,0 +1,13144 @@ + + + + FlatRedBall + + + + + A viiewer in a VisibilityGrid. This typically is a unit which removes fog of war around it. + + + + + Provides an interface for 3D position. + + + Does not include Velocity and Acceleration like IPositionable. + + + + + The absolute X position. + + + + + The absolute Y position. + + + + + The absolute Z position. + + + + + The distance in world units that this IViewer can see. + + + + + Represents a 2D grid of cells which identify what can be seen given a list of IViewers. + This supports line of sight. + + + + + Instantiates a new VisibilityGrid. + + The absolute x coordinate seed value. + The absolute y coordinate seed value. + The amount of distance in world coordinates between rows and columns. + Number of tiles wide (on the X axis) + Number of tiles heigh (on the Y axis) + + + + Checks if any viewers have changed since last Update, and if so it updates the grid. + + Whether anything has changed. + + + + Destroys the VisibilityGrid - specifically disposing its internal fog texture. + + + + + Adds an IViewer to this grid. + + The viewer to add. + + + + Makes walls visible if they are adjacent to visible non-walls. + + + + + Adds a block (or wall) at a given world location. + + The world coordinate X. + The world coordinate Y. + + + + Unblocks a tile that was previously marked as a world blocker + + The X coordinate of the tile + The Y coordinate of the tile + + + + Clears all blocked tiles. + + + + + Returns whether a given world position is in view of a given viewer. + + The viewer to check visibility for. + The world coordinates. + Whether in view. + + + + Returns whether a given world coordinate is relealed. + + The world x coordinate. + The world y coordinate. + Whether the world coordinate is revealed or not. + + + + Returns whether a given X and Y index is revealed. + + The x index + The y index + Whether the location specified by the x/y index is revealed. + + + + Hides the entire grid (makes it not revealed). + + + + + Reveals the entire grid. + + + + + Removes a viewer. + + The argument IViewer to remove. + + + + Reveals a circle around the given world coordinate using a given radius + + The world coordinate X + The world coordinate Y + The radius in world units + + + + Tests if two vector positions are within line of sight given a collision map. + + The first world-coordinate position. + The second world-coordinate position. + The list of polygons used to test if the two positions are within line of sight. + + + + + Tests if two vector positions are within line of sight given a collision map. + + The first world-coordinate position. + The second world-coordinate position. + Distance from position2 to the polygon it's colliding against. + If a polygon is within this threshold, this will return false. + The list of polygons used to test if the two positions are within line of sight. + + + + + Returns the midpoint between two Vector3s. + + The first position. + The connecting position. + The midpoint between the two positions. + + + + Calculates the closest visible point to outOfSightPosition given the currentPosition. + + The position with which to test Line Of Sight + The connector to the outOfSightPosition with which to find midpoint optimizations. + The guide to find the optimal in sight position. + The number of times we will midpoint optimize, higher means closer to optimal. + Usually the object using the path will be larger than 0, use the size of the collision for testing line of sight. + Polygon list which we will use for collision (without it, everything is straight line of sight). + + + + + Represents a one-way const-including path to a PositionedNode. + + + + + Creates a new Link. + + The node to link to. + The cost to travel the link. + + + + The cost to travel the link. + + + This is by default the distance to travel; however it can manually + be changed to be any value to reflect different terrain, altitude, or other + travelling costs. + + + + + The destination PositionedNode. The starting PositionedNode is not stored by the Link instance. + + + + + Stores a collection of PositionedNodes and provides common functionality for + pathfinding logic. + + + + + Defines that an object has a name. + + + Objects which are referenced by other objects in serializable classes + should be INameable so that the in-memory reference can be coverted to + a string and then re-created when the object is deserialized. + + + + + The name of the object. + + + + + Creates an empty NodeNetwork. + + + + + Creates a new PositionedNode and adds it to the NodeNetwork. + + The newly-created PositionedNode. + + + + Adds an already-created PositionedNode to the NodeNetwork. + + + Will not add the PositionedNode if it is already part of the NodeNetwork + + The PositionedNode to add. + + + + Finds a PositionedNode by the argument nameofNode. + + The name of the PositionedNode to search for. + The PositionedNode with the matching Name, or null if no PositionedNodes match. + + + + Returns the PositionedNode that's the closest to the argument position. + + The point to find the closest PositionedNode to. + The PositionedNode that is the closest to the argument position. + + + + Returns the radius of the PositionedNode visible representation Polygons. + + + The size of the PositionedNode visible representation Polygons depends on the + camera's Z position - as the Camera moves further away, the Polygons are drawn larger. + If the Camera is viewing down the Z axis then changing the Z will not affect the visible + size of the PositionedNode visible representation. + + The camera to use when calculating the size. + The index of the PositionedNode in the NodeNetwork. Since nodes can be in + 3D space the individual PositionedNode is required. + The radius of the node. + + + + Returns the List of PositionedNodes which make the path from the start PositionedNode to the end PositionedNode. + + + If start and end are the same node then the List returned will contain that node. + + The PositionedNode to begin the path at. + The destination PositionedNode. + The list of nodes to travel through to reach the end PositionedNode from the start PositionedNode. The + start and end nodes are included in the returned List. + + + + Returns the List of PositionedNodes which make the path from the start Vector3 to the end Vector3. + + + This method finds the closest nodes to each of the arguments, then calls the overload for GetPath which takes + PositionedNode arguments. + + The world-coordinate start position. + The world-coordinate end position. + The list of nodes to travel through to reach the closest PositionedNode to the endPoint from the closest + PositionedNode to the startPoint. + + + + Returns the List of PositionedNodes which make the path from the start PositionedNode to the end PositionedNode, or the node closest to the end node which is linked through the network to the start PositionedNode. + + + If start and end are the same node then the List returned will contain that node. + + The PositionedNode to begin the path at. + The optimal destination PositionedNode. + The list of nodes to travel through to reach the end PositionedNode, or the node closest to the PositionedNode which is connected to the start PositionedNode, from the start PositionedNode. The + start and end nodes are included in the returned List. + + + + Returns the List of PositionedNodes which make the path from the start Vector3 to the end Vector3, or to the node closest to the end Vector3 available if the end node is not linked in some way to the start node. + + + This method finds the closest nodes to each of the arguments, then calls the overload for GetPathOrClosest which takes + PositionedNode arguments. + + The world-coordinate start position. + The world-coordinate end position. + The list of nodes to travel through to reach the closest PositionedNode which is linked to the closest PositionedNode to the startPoint. + + + + + Returns a List of Vector3s that will be an optimized version of GetPath given the NodeNetwork and a Collision Map to test against. + + + This method will get the optimal path between the two vectors using GetPath, and then will optimize it by + testing line of sight between the nodes to see if the path can be optimized further (for more optimized + pathfinding). When optimizing the path between two nodes, it will check if the midpoint is in line of sight with the + startingPosition, and if it is, change the path to the midpoint instead of the current target node. the numberOfOptimizations + decides how many times it will take the midpoint and optimize further. + + This method assumes that the node network does NOT fall within collidable objects. + + The world-coordinate of the starting position. + The world-coordinate of the destination. + The collision map which will have the obstacles you are trying to path around. + + + + + Returns a List of Vector3s that will be an optimized version of GetPath given the NodeNetwork and a Collision Map to test against. + + + This method will get the optimal path between the two vectors using GetPath, and then will optimize it by + testing line of sight between the nodes to see if the path can be optimized further (for more optimized + pathfinding). When optimizing the path between two nodes, it will check if the midpoint is in line of sight with the + startingPosition, and if it is, change the path to the midpoint instead of the current target node. the numberOfOptimizations + decides how many times it will take the midpoint and optimize further. + + The world-coordinate of the starting position. + The world-coordinate of the destination. + The number of times the algorithm will take the midpoint between + two nodes to test if they are within line of sight of each other (higher means nearer to optimal path). + + Usually the object using the path will be larger than 0, use the size of the collision for testing line of sight. + The collision map which will have the obstacles you are trying to path around. + + + + + Removes the argument PositionedNode from the NodeNetwork. Also destroys any links + pointing to the argument PositionedNode. + + The PositionedNode to remove. + + + + Removes the argument PositionedNode from the NodeNetwork. Also destroys any links + pointing to the argument PositionedNode. + + The PositionedNode to remove from the network. + Scans the entire network and removes all links to this node. Used when not all link relationships are two way. + + + + Updates the visible representation of the NodeNetwork. This is only needed to be called if the NodeNetwork + is visible and if any contained PositionedNodes or Links have changed. + + + + + All nodes in this NodeNetwork. + + + + + The polygons used to represent PositionedNodes. This is populated and managed by the + NodeNetwork if Visible is set to true. + + + + + The Color that Node polygons should use when Visible is true; + + + + + Controls the visibility of the NodeNetwork. This is usually only set to + true for debugging and testing purposes. + + + Setting this value to true creates Polygons and Lines to represent the + NodeNetwork. Setting it back to false destroys all objects used for visible + representation. + + + + + An object which has position properties + + + + + The node that links to this node. This is reset every time the + containing NodeNetwork searches for a path. + + + + + The cost to get to this node from the start node. This variable is + set when the containing NodeNetwork searches for a path. + + + + + Only active nodes are included in pathfinding and find node searches. + + Update February 10, 2013 + Nodes should always start + out as active. + + + + Creates a new PositionedNode. + + + + + Disconnects all Links between this and the argument node. + + The PositionedNode to break links between. + + + + Returns whether this has a Link to the argument PositionedNode. + + + If this does not link to the argument PositionedNode, but the argument + links back to this, the method will return false. It only checks links one-way. + + The argument to test linking. + Whether this PositionedNode links to the argument node. + + + + Creates Links from this to the argument nodeToLinkTo, and another Link from the + argument nodeToLinkTo back to this. + + + If either this or the argument nodeToLinkTo already contains a link to the other + PositionedNode, then the cost of the link is set to the argument costTo. + + The other PositionedNode to create Links between. + The cost to travel between this and the argument nodeToLinkTo. + + + + Creates Links from this to the argument nodeToLinkTo, and another Link from the + argument nodeToLinkTo back to this. + + + If either this or the argument nodeToLinkTo already contains a link to the other + PositionedNode, then the cost of the link is set to the argument costTo or costFrom as appropriate. + + The other PositionedNode to create the Links between. + The cost to travel from this to the argument nodeToLinkTo. + The cost to travel from the nodeToLinkTo back to this. + + + + Creates a link from this PositionedNode to the argument nodeToLinkTo. Links + on the argument nodeToLinkTo are not modified. + + + If this already links to the arugment nodeToLinkTo, the cost is set to the argument + costTo. + + The PositionedNode to create a link to. + The cost to travel from this to the argument nodeToLinkTo. + + + + Returns the string representation of this. + + The string representation of this. + + + + Returns the cost to get to this node from the start node. This + value is only accurate if the node is contained in list returned + by the last call to NodeNetwork.GetPath. + + + This value is reset anytime GetPath is called on the containing NodeNetwork. + + + + + The Node's name. Mainly used for saving NodeNetworks since saved Links reference + PositionedNodes by name. + + + + + The X position of the PositionedNode. + + + + + The Y position of the PositionedNode. + + + + + The Z position of the PositionedNode. + + + + + The links belonging to this PositionedNode. + + + This is a list of Links which reference the PositionedNodes that this links to. + Links are one-way and PositionedNodes that this links to do not necessarily contain + Links back to this. + + + + + Only active nodes are included in pathfinding and find node searches. + + + + + Defines a member as an external instance. Members which are external instances + are not read by ContentReaders. Rather they're set by ExternalInstances of referenced + content such as Texture2Ds. + + + + + Attribute marking a member in a save class as an external instance. That is, it should + be treated as external content by the ObjectReaders and ObjectWriters in the FlatRedBall + content pipeline. + + + + + Used internally by the engine to determine the order of + reading and writing for instances. + + + + + Plays the argument sound effect. + + The sound effect to play + Volume, ranging from 0.0f (silence) to 1.0f (full volume). 1.0f is full volume + + + + Plays the current song. PlaySong with an argument must + be called before this can be called. This can be used to + resume music when the game is unpaused or if audio options are + being turned on/off + + + + + Adds a sound bank + + The sound bank to add + + + + Adds a wave bank + + The wave bank to add + + + + Removes a sound bank + + The sound bank to remove + + + + Removes a wave bank + + The wave bank to remove + + + + Whether or not the given sound bank file has been loaded + + The sound bank to check + Whether or not the sound bank has been loaded + + + + Whether or not the given wave bank file has been loaded + + The wave bank to check + Whether or not the wave bank has been loaded + + + + Gets a sound + + The name of the sound in the XACT project + A new sound + + + + Gets a sound + + The name of the sound in the XACT project + The name of the sound bank to retrieve the sound from + A new sound + + + + Gets a positioned sound + + The name of the sound in the XACT project + A new positioned sound + + + + Gets a positioned sound + + The name of the sound in the XACT project + The name of the sound bank to retrieve the sound from + A new positioned sound + + + + Removes the specified sound from the audio manager + Warning: The sound will stop being positioned when removed + + The sound to remove + + + + Plays a sound immediately + + The name of the sound in the XACT project + + + + Plays a sound immediately + + The name of the sound in the XACT project + The name of the sound bank to retrieve the sound from + + + + Initializes the audio manager with an XACT audio project + + The settings file for the audio project (xgs) + The wave bank file for the audio project (xwb) + The sound bank file for the audio project (xsb) + + + + Represents the "active" song. This may or may not be actually playing. + This may still be non-null when no song is playing if the code has stopped + music from playing. The AudioManager remembers this to resume playing later. + + + + + Represents the song that is currently playing. If no song is playing this is null. + + + + + Reports the total number of sound effects that have been played by the AudioManager since the start of the program's execution. + This can be used to count sound effect plays as a rough form of profiling. + + + + + Used to manage global variables in the audio engine + + + + + A sound which has a position enabling effects such as doppler effect and + distance-based volume adjustments. + + + + + Represents an object with position, rotation, ability to + store instructions, and attachment abilities. + + + The PositionedObject is a common object in FlatRedBall. It is the root class + for a number of FlatRedBall classes including Sprites, Texts, SpriteFrames, PositionedModels, + and all Shapes. + + The PositionedObject class is also used as the base class when creating custom Entitiy objects. + + + PositionedObjects can be automatically managed by being added to the SpriteManager. + + + + + + Interface for an object which can be attached to a parent. + + + IAttachables do not necessarily have to be positioned objects - they + can also be objects which have un-positioned attachments. For example, + an event in a scripted sequence might have a parent event which it executes + after. + + + + + Clears all attachments to parents and removes all attached Children. + + + + + Detaches the instance from its parent and removes it from its parent's Children List. + + + + + Forces an update of the instance and calls ForceUpdateDependencies on its parent. + + + This method will recursively crawl up the Parent property until it reaches the TopParent. + + + + + Gets all lists that the instance belongs to. + + + This property provides the two-way relationship between IAttachables and + and common FlatRedBall Lists. + + + + + Provides an interface for 3D position, velocity, and acceleration. + + + For an interface which provides only and no velocity and acceleration, see + FlatRedBall.Math.IStaticPositionable. + + + + + + Gets and sets the absolute X Velocity. Meausred in units per second. + + + + + Gets and sets the absolute Y Velocity. Meausred in units per second. + + + + + Gets and sets the absolute Z Velocity. Meausred in units per second. + + + + + Gets and sets the absolute X Acceleration. Meausred in units per second. + + + + + Gets and sets the absolute Y Acceleration. Meausred in units per second. + + + + + Gets and sets the absolute Z Acceleration. Meausred in units per second. + + + + + Provides an interface for objects which can be rotated in 3D space. Includes + absolute rotation values and rotational velocity values. + + + + + Gets and sets the absolute rotation matrix. + + + Implementers should mirror changes to the RotationMatrix in the + individual rotation values. + + + + + Gets and sets the rotation on the X Axis. + + + Implementors should mirror changes to invididual rotation values in the + RotationMatrix property. + + + + + Gets and sets the rotation on the Y Axis. + + + Implementors should mirror changes to invididual rotation values in the + RotationMatrix property. + + + + + Gets and sets the rotation on the Z Axis. + + + Implementors should mirror changes to invididual rotation values in the + RotationMatrix property. + + + + + Gets and sets the rotational velocity on the X Axis. + + + + + Gets and sets the rotational velocity on the Y Axis. + + + + + Gets and sets the rotational velocity on the Z Axis. + + + + + Provides an interface for objects which can store Instructions. + + + + + The list of Instructions that this instance owns. These instructions usually + will execute on this instance; however, this is not a requirement. + + + + + The absolute world position of the instance. + + + This mirrors the X, Y, and Z properties. This value essentially becomes + read-only if this object has a Parent. + + + + + The position of the object relative to its Parent in its Parent's coordinate space. + + + These values become the dominant controller of absolute Position if this + instance has a Parent. These values have no impact if the instance does not + have a Parent. + + + + + The absolute world velocity of the instance measured in units per second. + + + This mirrors the XVelocity, YVelocity, and ZVelocity properties. This value is essentially + invalid if this instance has a Parent. + + + + + The velocity of the object relative to its Parent in its Parent's cooridnate space. + + + This value modifies the RelativePosition field even if the object does not have a Parent. + + + + + The absolute world acceleration of the instance measured in units per second per second. + + + This mirrors the XAcceleration, YAcceleration, and ZAcceleration properties. This value is + essentially invalid if the instance has a Parent; however, it can still build up the Velocity + value. + + If an object with a parent has a non-zero Acceleration for a significant amount of time, then is + detached, the Velocity will likely be non-zero and may be very large causing the object to move + rapidly. + + + + + + The acceleration of the object relative to its Parent in its Parent's coordinate space. + + + This value modifies the RelativeVelocity field even if the object does not have a Parent. + + + + + The actual velocity of the object from the last frame to this frame. This value is only valid + if the KeepTrackOfReal property has been true for over one frame. + + + + + The actualy acceleration of the object from the last frame to this frame. This value is only valid + if the KeepTrackOfReal property has been set to true for over two frames. + + + + + The last Position of the object - used to calculate the RealVelocity field. This is + valid only if the KeepTrackOfReal property is true. + + + + + The last Velocity of the object - this is used to calculate the RealAcceleration field. This is + valid only if the KeepTrackOfReal property is true. + + + + + The drag of the instance. + + + + + + + + The name of the instance. + + + + + The X component of the object's absolute rotation. + + + + + The Y component of the object's absolute rotation. + + + + + The Z component of the object's absolute rotation. + + + + + The X component of the object's absolute rotational velocity. + + + + + The Y component of the object's absolute rotational velocity. + + + + + The Z component of the object's absolute rotational velocity. + + + + + The X component of the object's relative rotation. + + + + + The Y component of the object's relative rotation. + + + + + The Z component of the object's relative rotation. + + + + + The X component of the object's relative rotational velocity. + + + + + The Y component of the object's relative rotational velocity. + + + + + The Z component of the object's relative rotational velocity. + + + + + The matrix representing the absolute orientation of the instance. + + + + + The matrix representing the relative orientation of the object in parent space. This + has no impact if the Parent is null. + + + + + The lists that this instance belongs to. This is how two-way relationships are implemented. + + + + + The PositionedObject that this is attached to. If it is null then this does not + follow any relative properties. + + + + + The objects that are attached to this instance. + + + + + The value that was last used when calling UpdateDependencies. + + + + + The instructions that belong to this instance. + + + + + Creates a new PositionedObject instance. + + + + + Determines whether the tree structure created by attachments of two PositionedObjects are identical. + + + This method does not investigate the PositionedObjects any more than looking at their children. It traverses + through the trees multiple times, and can be a very slow method if the PositionedObjects have large subtrees. + The subtrees are small enough in most cases + where there won't be a performance issue. + + The first PositionedObject. + The second PositionedObject. + Whether the tree structure created by attachments is the same. + + + + Attaches this PositionedObject to the argument newParent. + + + A useful way to understand the affect of changeRelative is to consider that it is the opposite of whether + absolute values change. That is, if the relative values do not change upon attachment, the absolute values + will change. + + For an example, consider a situation where a child has an absolute x value of 5 and a relative value of 0 and + a parent has an absolute x value of 0. If the child is attached to the parent + and relative is not changed (relative x remains at 0), then the child's x will be + parent.x + child.relX, or 0 + 0 = 0. We see that the relative value didn't + change, so the absolute did. + + + If, on the other hand, the relative was changed, the absolute position would be the same. + Since the parent's absolute position was 0 and the absolute position of the + child should remain at 5, then the relX value would change to 5 (assuming + the parent isn't rotated). + + + The PositionedObject to attach to. + Whether relative values should change so that absolute values stay the same. + + + + Detaches this PositionedObject from its parent and detaches all of the PositionedObject's + Children. + + + + + Copies most internal fields of this instance to the argument PositionedObjects. + + + The following fields are not copied: + * Name + * ListsBelongingTo + * Parent + * Children + * Instructions + + + + + + Copies the absolute position and rotation values to the relative values. + + + + + Creates a clone of this instance typed as a T. + + The type of the new object. + The newly created instance. + + + + Creates a new Children PositionedObjectList. + + + This method is only necessary if a PositionedObject + is manually cloned by calling MemberwiseClone. This + should not be called to detach children as the two-way + relationship between PositionedObject and PositionedObjectList + will keep the old Children PositionedObjectList in scope resulting + in a memory leak. + + + + + Creates a new InstructionList. + + + This method is only necessary if a PositionedObject + is manually cloned by calling MemberwiseClone. This + should not be called to clear the Instructions property + as it will create a new instance and get rid fo the old one + resulting in unnecessary garbage collection. + + + + + Detaches the PositionedObject from its parent PositionedObject. + + + This method cleans up the two way relationship between parent and child. + + + + + Executes instructions according to the argument currentTime and cycles and reorders + Instructions as necessary. + + The current time to compare the instruction's TimeToExecute against. + + + + Forces an update of the PositionedObject's absolute position and rotation values + according to its attachment and relative values. + + + The absolute positions and rotations of Sprites are updated in the + Sprite.UpdateDependencies method which is + called in the SpriteManager.UpdateDependencies. The SpriteManager.UpdateDependencies is called + once per frame by default in the Sprite's regular activity. This method only needs to be called + if changes are made after + the UpdateDependencies method has been called for that particular frame or if updated + positions are needed + immediately after relative values or attachments have been changed. + + This method will recur up the hierarchical PositionedObject struture stopping + when it hits the top parent. + + + + + Fills the argument list with the instance's parent, grandparent, etc. recursively. + + The list to fill. + + + + Resets all properties to their default values and clears the ListsBelongingTo property. + + + + + Resets all properties to their default values. + + Whether the instance should clear its ListsBelongingTo property. + + + + Determines whether this is a parent (or grandparent of any level) of the argument + PositionedObject + + The PositionedObject to test whether it is lower + in the same hiearchical structure. + Whether the attachable argument is a child of this instance. + + + + Stops all automatic behavior and stores the necessary instructions to + resume activity in the argument InstructionList. + + The List to store instructions which are executed to + resume activity. + + + + Removes this instance from all Lists that it shares two-way + relationships with. + + + FlatRedBall managers use this method in Remove methods. + + + + + Sets the internal storage of the last frame's position and velocity + so that the next frame's real velocity and acceleration values will be + Vector3.Zero. + + + + + Copies all values related to "Real" values from the argument + PositionedObject to this instance. + + The PositionedObject to copy Real values from. + + + + Uses the object's absolute position and orientation along with its + Parent's orientation and position to update its relative position and orientation. + + + + + Performs the every-frame position and rotation changing activity. + + + This method does not need to be explicitly called + for managed objects such as Sprites and collision shapes. + This method is exposed for custom PositionedObjects which + are not added to a Manager. + + The amount of time since last frame. + Pre-calculated ((secondDifference*secondDifference) ^2) / 2. + The last frame secondDifference. + + + + Performs the every-frame relative position and relative rotation changing activity. + + + This method does not need to be explicitly called + for managed objects such as Sprites and collision shapes. + This method is exposed for custom PositionedObjects which + are not added to a Manager. + + The amount of time since last frame. + Pre-calculated ((secondDifference*secondDifference) ^2) / 2. + + + + Returns a string containing common information about the PositionedObject. + + The string containing the information about this object. + + + + Updates the absolute position and rotation using relative values and the Parent PositionedObject. + + + This method recurs up the hierarchical chain calling UpdateDependencies so that the entire family of + PositionedObjects is positioned appropriately. + + + + + Sets the absolute rotation values according to the object's RotationMatrix. + + + + + The list of PositionedObjects that are attached to this instance. + + + + + The List of IAttachableRemovables (containers) that this shares a two-way relationship with. + + + + + The instance's name. + + + + + The PositionedObject that this instance is attached to. + + + + + Whether the parent's rotation should change the object's position. + + + + + Gets and sets whether the parent's rotation should change the object's rotation. + + + + + Gets and sets the X position relative to the instance's parent. + + + If the instance does not have a parent this property has no effect. + + + + + Gets and sets the Y position relative to the instance's parent. + + + If the instance does not have a parent this property has no effect. + + + + + Gets and sets the Z position relative to the instance's parent. + + + If the instance does not have a parent this property has no effect. + + + + + Gets and sets the rate of change of the RelativeX property in units per second. + + + + + Gets and sets the rate of change of the RelativeY property in units per second. + + + + + Gets and sets the rate of change of the RelativeZ property in units per second. + + + + + Gets and sets the rate of change of the RelativeXVelocity property in units per second. + + + + + Gets and sets the rate of change of the RelativeYVelocity property in units per second. + + + + + Gets and sets the rate of change of the RelativeZVelocity property in units per second. + + + + + Gets and sets the rotation on the X axis relative to the instance's parent. + + + + + Gets and sets the rotation on the Y axis relative to the instance's parent. + + + + + Gets and sets the rotation on the Z axis relative to the instance's parent. + + + + + The rotation representing the orientation relative to the instance's Parent. + + + If the instance does not have a Parent then this property has no effect. + + + + + Gets and sets the rate of change of the RelativeRotationX property in units per second. + + + + + Gets and sets the rate of change of the RelativeRotationY property in units per second. + + + + + Gets and sets the rate of change of the RelativeRotationZ property in units per second. + + + + + The last time Update was called. + + + This value is set through the TimeManager's CurrentTime property. + + + + + The x rotation of a PositionedObject + + + This rotates the PositionedObject about the X axis. Rotation is represented in + radians. Angles will always be greater than or equal to 0 and less than + two times PI. Values outside of these bounds will be regulated by the + set property. + + RotationX can be used to flip an image of a PositionedObject, but a PositionedObject should not + be flipped during animation (if it is a Sprite). AnimationFrames can be flipped without + setting a Sprite's Rotation values. + + + + + The y rotation of a PositionedObject + + + This rotates the PositionedObject about the Y axis. Rotation is represented in + radians. Angles will always be greater than or equal to 0 and less than + two times PI. Values outside of these bounds will be regulated by the + set property. + + RotationY can be used to flip an image and set it upside down, but a PositionedObject + should not be flipped during animation (if it is a Sprite). AnimationFrames can be flipped without + setting a Sprite's Rotation values. + + + + + The z rotation of a PositionedObject + + + This rotates the PositionedObject about the Z axis. Rotation is represented in + radians. Angles will always be greater than or equal to 0 and less than + two times PI. Values outside of these bounds will be regulated by the + set property. + + RotationZ can be used to "spin" a PositionedObject, with a positive variable spinning + clockwise. + + + + + The matrix applied to the object resulting in its final orientation. + + + The RotationMatrix and RotationX, RotationY, RotationZ reflect eachother. Changing one will change the other. + + + + + + + + Gets or sets the overall transformation of this object + + + Changing the transformation matrix will change rotation, position and scaling + + + + + Gets or sets the overall transformation of this object, relative to its parent + + + Changing the transformation matrix will change rotation, position and scaling + + + + + The absolute X rotation speed measured in radians per second + + + The RotationXVelocity variable is how fast a PositionedObject is rotating on the X axis. It is + measured in radians per second. + + + + + The absolute Y rotation speed measured in radians per second + + + The RotationYVelocity variable is how fast a PositionedObject is rotating on the Y axis. It is + measured in radians per second. + + + + + The absolute Z rotation speed measured in radians per second + + + The RotationZVelocity variable is how fast a PositionedObject is rotating on the Z axis. It is + measured in radians per second. + + + + + + Returns the top node in the attachment hierarchical relationship + + + + + The absolute X position. + + + + + The absolute Y position. + + + + + The absolute Z position. + + + + + Gets and sets the absolute X Velocity. Meausred in units per second. + + + + + Gets and sets the absolute Y Velocity. Meausred in units per second. + + + + + Gets and sets the absolute Z Velocity. Meausred in units per second. + + + + + Gets and sets the absolute X Acceleration. Meausred in units per second. + + + + + Gets and sets the absolute Y Acceleration. Meausred in units per second. + + + + + Gets and sets the absolute Z Acceleration. Meausred in units per second. + + + + + Whether the PositionedObject's RealVelocity and RealAcceleration are + updated every frame. + + + + + Linear approximation of drag. This reduces the Velocity of the + instance according to its absolute Velocity. + + + The following formula is applied to apply Drag: + + Velocity -= Velocity * Drag * TimeManager.SecondDifference; + + + + + + + The list of Instructions that this instance owns. These instructions usually + will execute on this instance; however, this is not a requirement. + + + + + Begins playback of this sound, or resumes playback (if it has been paused) + + + + + Pauses playback of this sound + + + + + Stops playing this sound immediately + + + + + Stops playing this sound as authored in the XACT project + + + + + Stops playing this sound immediately + + + + + Gets or sets a scalar applied to the level of Doppler effect calculated + between this sound and a listener. + + + + + Used to manage variables in a sound + + + + + Begins playback of this sound, or resumes playback (if it has been paused) + + + + + Pauses playback of this sound + + + + + Stops playing this sound as authored + + + + + Stops playing this sound immediately + + + + + Stops playing this sound as authored in the XACT project + + + + + Sets the aspectRatio to match the width/height of the area that the camera is drawing to. + + + This is usually used in applications with split screen or when on a widescreen display. + + + + + Determines if the X value is in view, assuming the camera is viewing down the Z axis. + + + Currently, this method assumes viewing down the Z axis. + + The absolute X position of the point. + The absolute Z position of the point. + + + + + Determines if the Y value is in view, assuming the camera is viewing down the Z axis. + + + Currently, this method assumes viewing down the Z axis. + + The absolute Y position of the point. + The absolute Z position of the point. + + + + + Returns the number of pixels per unit at the given absolute Z value. Assumes + that the Camera is unrotated. + + + If using the PixelsPerUnitAt for a rotated camera, use the overload which + takes a Vector3 argument. + + The absolute Z position. + The number of pixels per world unit (perpendicular to the camera's forward vector). + + + + Sets the camera to Orthogonal, sets the OrthogonalWidth and + OrthogonalHeight to match the argument values, and can move the + so the bottom-left corner of the screen is at the origin. + + Whether the camera should be repositioned + so the bottom left is at the origin. + The desired unit width of the view. + The desired unit height of the view. + + + + Whether or not lighting is enabled for this camera + + + + + Adds a layer to the Camera. This method does not remove layers that already + exist in the SpriteManager. + + The layer to add. + + + + Supplied sprites are billboarded using the camera's RotationMatrix. + + + + + Supplied Sprites are billboarded using the camera's RotationMatrix. + + + + + Removes all visibility borders. + + + + + + Moves a Sprite so that it remains fully in the camera's view. + + + This method does not consider Sprite rotation, negative scale, or situations + when the camera is not looking down the Z axis. + + The Sprite to keep in view. + + + + Positiones the argument positionable randomly in camera between the argument bounds. + + + Assumes the camera is viewing down the Z plane - it is unrotated. + + The object to reposition. + The closest possible distance from the camera. + The furthest possible distance from the camera. + + + + Removes the argument Layer from this Camera. Does not empty the layer or + remove contained objects from their respective managers. + + The layer to remove + + + + Sets the visible borders when the camera is looking down the Z axis. + + + This sets visibility ranges for the camera. That is, if the camera's maximumX is set to 100 at a zToSetAt of + 0, the camera will never be able to see the point x = 101, z = 0. The camera imposes these limitations + by calculating the actual minimum and maximum values according to the variables passed. Also, + the camera keeps track of these visible limits and readjusts the mimimum and maximum values + when the camera moves in the z direction. Therefore, it is only necessary to set these + values once, and the camera will remeber that these are the visibility borders, regardless of + its position. It is important to note that the visiblity borders can be violated if they are too + close together - if a camera moves so far back that its viewable area at the set Z is greater than + the set minimumX and maximumX range, the camera will show an area outside of this range. + + + The minimum x value of the visiblity border. + The minimum y value of the visiblity border. + The maximum x value of the visiblity border. + The maximum y value of the visiblity border. + The z value of the plane to use for the visibility border. + + + + Copies all fields from the argument to the camera instance. + + + This method will not copy the name, InstructionArray, or children PositionedObjects + (objects attached to the cameraToSetTo). + + The camera to clone. + + + + Sets the viewport for this camera to a standard split-screen viewport + + The viewport to use for this camera + + + + Calculates the minimum and maximum X values for the camera based off of its + base values (such as mBaseMaximumX) and its current view + + + + + Updates the destination rectangle (for the viewport). Also fixes the aspect ratio. + + + + + Gets and sets the top side of the destination rectangle (where on the window + the camera will display). Measured in pixels. Destination uses an inverted Y (positive points down). + + + + + Gets and sets the bottom side of the destination rectangle (where on the window + the camera will display). Measured in pixels. Destination uses an inverted Y (positive points down). + + + + + Gets and sets the left side of the destination rectangle (where on the window + the camera will display). Measured in pixels. + + + + + Gets and sets the right side of the destination rectangle (where on the window + the camera will display). Measured in pixels. + + + + + Represents the top left justified area the Camera will draw over. + + + This represents the area in pixel coordinates that the camera will display relative + to the top left of the owning Control. If the Control is resized, the camera should modify + its DestinationRectangle to match the new area. + + + Multiple cameras with different DestinationRectangles can be used to display split screen + or picture-in-picture. + + + + + + The width/height of the view of the camera + + + This determines the ratio of the width to height of the camera. By default, the aspect ratio is 4/3, + but this should be changed for widescreen monitors or in situations using multiple cameras. For example, if + a game is in split screen with a vertical split, then each camera will show the same height, but half the width. + The aspect ratio should be 2/3. + + + + + Whether the camera draws its layers. + + + + + Whether the Camera draws world objects (objects not on the Camera's Layer) + + + + + Whether the Camera draws shapes + + + + + The Y field of view of the camera in radians. Field of view represents the + Y angle from the bottom of the screen to the top. + + + This modifies the xEdge and yEdge properties. Default value is (float)Math.PI / 4.0f; + + + + + A Camera-specific layer. Objects on this layer will not appear + in any other cameras. + + + This instance is automatically created when the Camera is instantiated. + + + + + A class containing options used when programatically generating a "Save" class. + + + + + + + + An XML-Serializable object representing the state of a Link. + + + + + Save class for the PositionedNode class. This class is used in the + .nntx (Node Network XML) file type. + + + + + Reader class used in the FlatRedBall Content Pipeline to convert compiled + AnimationChainLists to the runtime instances. + + + + + Base class for AnimationChainListSave and AnimationChainListSaveContent. + + + + + Create a "save" object from a regular animation chain list + + + + + The base class for AnimationChainSave and AnimationChainSaveContent. + + + + + This is used if the AnimationChain actually comes from + a file like a .gif. + + + + + The base class for AnimationFrameSave and AnimationFrameSaveContent. + + + + + Whether the texture should be flipped horizontally. + + + + + Whether the texture should be flipped on the vertidally. + + + + + Used in XML Serialization of AnimationChains - this should + not explicitly be set by the user. + + + + + The amount of time in seconds the AnimationFrame should be shown for. + + + + + The left coordinate in texture coordinates of the AnimationFrame. Default is 0. + + + + + The right coordinate in texture coordinates of the AnimationFrame. Default is 1. + + + + + The top coordinate in texture coordinates of the AnimationFrame. Default is 0. + + + + + The bottom coordinate in texture coordinates of the AnimationFrame. Default is 1. + + + + + The relative X position of the object that is using this AnimationFrame. This + is only applied if the IAnimationChainAnimatable's UseAnimationRelativePosition is + set to true. + + + + + The relative Y position of the object that is using this AnimationFrame. This + is only applied if the IAnimationChainAnimatable's UseAnimationRelativePosition is + set to true. + + + + + ContentLoadBatch allows for batch loading of resources through the Content Pipeline as well as from file. Loading can be asynchronous if + specified by using the LoadAsync method. + + + + + Removes an IDisposable from the ContentManager. This method does not call Dispose on the argument Disposable. It + must be disposed + + The IDisposable to be removed + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Matches the marked member with the member to set when this instance is loaded through + the content pipeline. This attribute is usually applied to ExternalReference objects. + + + + + Save class which stores AnimationSequence information. + + + + + The file that this InstructionSetSave was deserialized from. + + + + + An XML serializable list of InstructionSetSaves. + + + + + Save class for a Keyframe (A list of Instructions). + + + + + A savable class containing the information for a MemberCondition. + + The type of the property to be compared. + + + + A savable reference to a TimedKeyframe. This does not actually store the contents + of the TimedKeyframe; instead it stores a string reference. + + + + + Save class storing SpotLight information + + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Delegate representing the method that is to be called for a property to be mapped. + This can be applied by specific type or by generic type. + + The member info of the object being mapped. + The instance being mapped. + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Deserializes a file into a new ShapeCollectionSave and returns it. + + The absolute or relative file name. If the file name is relative, then the FileManager's RelativeDirectory will be used. + The newly-created ShapeCollectionSave. + + + + A savable representation of a Spline. This is used in SplineSaveLists. + + + + + Save class for lists of Splines. + + + + + Represents the base class for saving material information. + + + This class is used as the base class for the MaterialSave class. This is used + as the base class for MaterialSave (runtime save class) and MaterialSaveContent + (content pipeline class). + + + + + The ordered list of effects to apply to Models. + + + The ordering is performed by Mesh and by MeshPart. That is, if there were + a PositionedModel with multiple Meshes and multiple MeshParts per mesh, then + the application might go as follows: + + model.Mesh[0].MeshPart[0].Effect = EffectFrom(EffectFiles[0]); + model.Mesh[0].MeshPart[1].Effect = EffectFrom(EffectFiles[1]); + model.Mesh[1].MeshPart[0].Effect = EffectFrom(EffectFiles[2]); + model.Mesh[1].MeshPart[1].Effect = EffectFrom(EffectFiles[3]); + model.Mesh[1].MeshPart[2].Effect = EffectFrom(EffectFiles[4]); + // And so on + + + + + + The ordered list of EffectParameterLists to apply. + + + See the EffectFiles documentation for an example of the order in which + the EffectParameterFiles are applied. + + + + + + The .matx file that this instance was created from if created from a file. + + + The MaterialSaveBase stores its source file name so that it can re-save itself + easily if it is embedded in another file like a .scnx file. + + + + + Model Material Desicribes the Material to use for a Model Mesh Part. + + + + + Model Material Desicribes the Material to use for a Model Mesh Part. + + The texture file name of the location of the texture to use + The diffuse color to use when no Texture is to be used. + The Color of the specular highlighting + The Color of the ModelMesh part will emit + The amount of Specular Color to Show. + How "Shiny" the specularity will be. + + + + The File name and location of the texture file to use, when setting the Texture on the + ModelMeshes Default Effect. + + + + + The Default Diffuse Color to use, when No Texture is Available for the Mesh Part. + + + + + The Default Specular Color to use. + + + + + The Default Emissive Color to use. + + + + + The Amount of power to force the Specular Color to use, within the ModelMeshes Default Effect. + + + + + The Shininess of the Specular Color within the ModelMeshes Default Effect. + + + + + The Material ID, that is within the Models Effect Collection + + + + + Save class for information about ModelMeshes. This contains a list of ModelMeshPartSaves. + + + + + Class used to read a NodeNetwork through the content pipeline. + + + + + Used to perform methods defined in a MappingMethods class on properties within a class. + + + The ObjectMapper can be used to perform tasks like code generation and serialization of objects. + + + + + Pass an object and a MappingMethods and MapObject will map each member in that object to the corresponding + method from the mappingMethods. + + object whose members will be called on. + MappingMethods which has dictionaries of members/methods. + + + + Class used by the FlatRedBall pipeline to read in XNBs. + + + + + Save class for the EmissionSettings class. This class is used + in the .emix (Emitter XML) file. + + + + + Base class for EmitterSave and EmitterSaveContent. + + The type for the ParticleBlueprint. This should be or inherit SpriteSaveBase. + + + + Stores the source .emix file name. This + is set if an EmitterSaveList is loaded from file. + + + + + Returns a List of files referenced by this. + + Whether the files should be absolute or relative. + The list of referenced files. + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Save class storing information for a BitmapFont. + + + + + The "save" class for HeightMaps used for XML serialization/deserialization. + + + + + An XML Serializable class representing the state of a Text. + + + + + Base class for TextSave and TextSaveContent. + + + + + Class used to read Scenes from XNB files. This class is used when + loading Scenes through the content pipeline. + + + + + An XML serializable "Save" class which can be included in other Save classes to store Camera information. + + + + + The absolute X position. + + + + + The absolute Y position. + + + + + The absolute Z position. + + + + + Whether the Camera is using an orthogonal projection matrix. If this is false, the Camera is using a perspective projection matrix. + + + + + The orthogonal height of the camera's view. + + + + + Creates a new CameraSave. This is used by the XmlSerializer when deserializing an XML. + Usually CameraSaves are created using the FromCamera static method. + + + + + Creates a new CameraSave instance using members from the passed Camera argument. + + The Camera to copy properties from. + + + + Sets the argument Camera's properties to the properties stored in this CameraSave. + + + Usually "Save" classes include a To[RuntimeType] method. The CameraSave does not follow + this pattern because it's most common that a CameraSave is loaded when a Camera is already + created by the engine. In this case, it's not very convenient to have to destroy the existing + Camera and replace it by a new instance. Instead, the SetCamera method will simply set the properties + on an existing Camera + + The Camera to set the properties on. + + + + This is the class that is serialized and deserialized to/from the XML file representing a scene. + + + + + DynamicSpriteList for compatability with FlatRedBall Managed DirectX. + + + The DynamicSpriteList and SpriteList will be combined into one list later + in the content pipeline. + + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Class that defines a source and destination file relationship. This can be used + by tools (such as Glue) which maintain the source/destination relationship between files. + + + + + The source file - the file which will be built to create the destination. + + + + + The destination file - the file which will be created when the source is built. + + + + + Instantiates a new SourceReferencingFile instance. + + + + + Instantiates a new SourceReferencingFile instance using the argument source and destination files. + + The source file name to use. + The destination file name to use. + + + + Serves as the base class for SpriteFrameSave and SpriteFrameSaveContent. + + The type of the ParentSprite. This is generic because + for SpriteFrameSave the generic type is SpriteSave, but for SpriteFrameSaveContent the + generic type is SpriteSaveContent. + + + + The BorderSides that this instance represents. + + + + + + + + The SpriteSaveBase that stores most of the Properties for the SpriteSave. + + + The SpriteSaveBase is used as a storage of properties because nearly all of its + properties are also used by SpriteFrames. Therefore, to prevent a lot of copy/paste, + this class is used to store properties. + + + + + Base class for SpriteGridSave and SpriteGridSaveContent. + + The type of the blueprint. + + + + Specifies the grid to use. + + + If the grid is 'y', use an XY grid. Otherwise an XZ. + + + + + The name of the SpriteGridSave. + + + + + This is used by the content pipeline to know which directory to look in + for Textures. Otherwise this variable is unused. + + + + + Constructs a new PoseChain. + + + + + Appends the argument Pose to the PoseChain then sorts the contained Poses by time. + + The pose to add + The index of the newly-added pose. + + + + [OBSOLETE] Gets the pose at the argument index. + + The index of the pose to get. + The pose at the argument index. + + + + Returns the number of Poses in the current PoseChain. + + + + + Inserts a pose at the argument Index. + + + Unlike the Add method, this method does not sort the + PoseChain after the addition. However, if Add is called + at a later time the PoseChain will be sorted. + + The index where the pose should be inserted. + The pose to insert. + + + + Removes the argument Pose from the PoseChain. + + The pose to be removed. + + + + Sorts the contained Poses by their time. + + + + + Returns information about the PoseChain. + + The string info about the argument PoseChain. + + + + Provides indexer access to the contained Poses. + + The index of the Pose to get. + The pose at the specified index. + + + + Gets the number of Poses contained in the PoseChain. + + + + + Provides access to the contained Poses. + + + + + The Name of the PoseChain. + + + + + The game time in systems when the Animation has been started. + + + + + Gets total length in seconds of the PoseChain. + + + + + Reader class used in the FlatRedBall Content Pipeline to convert compiled + SpriteRigs to the runtime instances. + + + + + Reads a sprite rig from a ContentReader + + + + + + + + Class used by FlatRedBall to read Sprites out of a .XNB file when used + read in through the content pipeline. This is used when deserializing Scenes. + + + + + Base class for SpriteRigSave and SpriteRigSaveContent. + + The type of the Sprite. Can be SpriteSave or SpriteSaveContent. + + + + The name of the Root sprite of the SpriteRig. + + + + + Whether the root is visible - often used for debugging or in tools + + + + + Whether joints are visible - often used for debugging or in tools. + + + + + Whether the assets referenced by the SpriteRigSaveBase should be serialized as relative to file. + + + + + Whether the SpriteRigSaveBase uses a right-handed coordinate system. + + + + + The TimeMeasurementUnit used by the SpriteRigSaveBase. + + + + + The file name for this SpriteRigSaveBase + + + + + Instantiates a new SpriteRigSave. + + + + + + + + + + Returns a list of files referenced by this instance. + + Whether the returned files should be absolute or relative. + All referenced files. + + + + Saves this file to a XML file on disk. + + The name of the file to save to. + + + + Loads the argument file and converts it to a SpriteRigSave. + + The file to load. + The loaded file. + + + + Converts this to a SpriteRig using the contentManagerName. + + The content manager to use when loading referenced files. + The resulting SpriteRig. + + + + This class will be instantiated by the XNA Framework Content + Pipeline to read the specified data type from binary .xnb format. + + Unlike the other Content Pipeline support classes, this should + be a part of your main game project, and not the Content Pipeline + Extension Library project. + + + + + Interface for an object which provides a visual interface for viewing and editing + an instance. This is the base type for PropertyGrids and ListDisplayWindows. + + + + + Exposes the object that is being displayed (casted as an object). + This exists so that a list of IObjectDisplayers has access to the + object displaying. This should be explicitly implemented. + + + + + Container class implements the IServiceProvider interface. This is used + to pass shared services between different components, for instance the + ContentManager uses it to locate the IGraphicsDeviceService implementation. + + + + + This is *NOT* secure, keys in code can easily be obtained by disassembling the game. + + + + + Used to initialize FlatRedBall without rendering anything to the screen + + The game + + + + Used to initialize FlatRedBall with a game + + The game + The graphics device manager + + + + Used to initialize FlatRedBall with a game and graphics options + + The game + The graphics device manager + The graphics options to use for this game + + + + Used to intialize FlatRedBall without a game object (e.g. in windows forms projects) + + The graphics device service + The window handle + + + + Used to intialize FlatRedBall without a game object (e.g. in windows forms projects) and with graphics options + + The graphics device service + The window handle + The graphics options + + + + Basic initialization steps (commond to all initialization methods) + + + + + Attempts to replace the texture. Will only work if the texture is loaded from file. + + Reference to the old texture + Reference to the new texture + The name of the content manager containing the texture + + + + The update method for command line games. Does not render anything to the screen + + The GameTime + + + + Calls UpdateDependencies on all contained managers. + + + + + Salt value that is combined with the EncryptionKey string for generating encryption keys + + + + + Password to use for decrypting files (set this to the appropriate value before attempting to load any CSV files that were encrypted in the content pipeline) + + + + + Represents a collection of AnimationFrames which can be used to perform + texture flipping animation on IAnimationChainAnimatables such as Sprites. + + + + + Creates an empty AnimationChain. + + + + + Creates a new AnimationChain with the argument capacity. + + Sets the initial capacity. Used to reduce memory allocation. + + + + Searches for and returns the AnimationFrame with its Name matching + the nameToSearchFor argument, or null if none are found. + + The name of the AnimationFrame to search for. + The AnimationFrame with matching name, or null if none exists. + + + + Returns the shortest absolute number of frames between the two argument frame numbers. This + method moves forward and backward and considers looping. + + The index of the first frame. + The index of the second frame. + The positive or negative number of frames between the two arguments. + + + + Sets the frameTime of each AnimationFrame in the AnimationChain to the passed value. + + + + + Gets the last AnimationFrame of the AnimationChain or null if + there are no AnimationFrames. + + + + + The name of the AnimationChain. + + + + + Returns the sum of the FrameLengths of all contained AnimationFrames. + + + + + A list of AnimationChains. + + + This class is often used by IAnimationChainAnimatables to store a list of + AnimationChains. Since the AnimationChainList provides a string indexer, it + is common to get a reference to an AnimationChain by its name and set it as the + IAnimationChainAnimatable's current AnimationChain. + + + + + Instantiates a new AnimationChainList. + + + + + Instantiates a new AnimationChainList. + + Sets the initial capacity to reduce memory allocation when subsequently calling Add. + + + + Gets and sets whether the AnimationChainList will save the + Texture2Ds that its AnimationFrames reference with names relative + to the .achx. Otherwise, this property is not used during runtime. + + + + + Gets or sets the TimeMeasurementUnit. This defaults to TimeMeasurementUnit.Millisecond and + should not be changed. It is included for compatability with older versions of FlatRedBall. + + + + + Gets and sets the intance's name. + + + + + Gets the AnimationChain by name. Returns null if no AnimationChain is found. + + The name of the AnimationChain to return + Reference to the AnimationChain with the specified name. + + + + Stores information about one frame in a texture-flipping animation. + + + Includes + information about which Texture2D to show, whether the Texture2D should be flipped, + the length of time to show the Texture2D for, texture coordinates (for sprite sheets), and + relative positioning. + + + + + Empty AnimationFrame. + + + + + The texture that the AnimationFrame will show. + + + + + Whether the texture should be flipped horizontally. + + + + + Whether the texture should be flipped on the vertidally. + + + + + Used in XML Serialization of AnimationChains - this should + not explicitly be set by the user. + + + + + The amount of time in seconds the AnimationFrame should be shown for. + + + + + The left coordinate in texture coordinates of the AnimationFrame. Default is 0. + + + + + The right coordinate in texture coordinates of the AnimationFrame. Default is 1. + + + + + The top coordinate in texture coordinates of the AnimationFrame. Default is 0. + + + + + The bottom coordinate in texture coordinates of the AnimationFrame. Default is 1. + + + + + The relative X position of the object that is using this AnimationFrame. This + is only applied if the IAnimationChainAnimatable's UseAnimationRelativePosition is + set to true. + + + + + The relative Y position of the object that is using this AnimationFrame. This + is only applied if the IAnimationChainAnimatable's UseAnimationRelativePosition is + set to true. + + + + + Creates a new AnimationFrame. + + + + + Creates a new AnimationFrame. + + The Texture2D to use for this AnimationFrame. + The amount of time in seconds that this AnimationFrame will display for when + it is used in an AnimationChain. + + + + Creates a new AnimationFrame. + + The string name of the Texture2D to use for this AnimationFrame. + This will be loaded through the content pipeline using the arugment contentManagerName. + The amount of time in seconds that this AnimationFrame will display for when + it is used in an AnimationChain. + The content manager name to use when loading the Texture2D . + + + + Creates a new AnimationFrame with identical properties. The new AnimationFrame + will not belong to the AnimationChain that this AnimationFrameBelongs to unless manually + added. + + The new AnimationFrame instance. + + + + Returns a string representation of this. + + String representation of this. + + + + Represents an object that can use AnimationChains for texture-flipping animation. + + + + + Whether animation is currently turned on. + + + + + Gets all animations stored in this. + + + + + Gets and sets how fast AnimationChains will animate. Default is 1. A value + of 2 will result in AnimationChains animating twice as fast. + + + + + Gets and sets the index of the current AnimationChain. + + + + + Gets the current AnimationChain. + + + + + Gets the name of the current AnimationChain or sets the current AnimationChain by name. + + + + + Gets and sets the current AnimationFrame index. + + + + + Gets whether the current AnimationFrame just changed this frame due to animation. + + + + + Gets whether the current AnimationChain just cycled (looped) this frame due to animation. + + + + + Whether the current AnimationFrame's relative position values (RelativeX and RelativeY) are applied + when animating. + + + + + Larger comes first. + + The first instance. + The second instance. + -1 if the first comes first, 1 if the second comes first, 0 if they're equal. + + + + Used to draw assets + Batch is sorted by Z with sprites and text + + The currently drawing camera + + + + Used to update the drawable batch + + + + + Used to destroy any assets that need to be destroyed + + + + + The X value to use for sorting. This does NOT affect the position + of objects drawn by the DrawableBatch. + + + + + The Y value to use for sorting. This does NOT affect the position + of objects drawn by the DrawableBatch. + + + + + The Z value to use for sorting. This does NOT affect the position + of objects drawn by the DrawableBatch. + + + + + Whether or not this batch should be updated + + + + + Caches effects and effect parameters + + + + + Initializes parameter name strings + + + + + Whether or not shared parameters should be cached + + + + + The model cached, if any + + + + + The effect cached, if any + + + + + Caches all effect parameters for faster lookup, by effect + + + + + Caches all effect parameters for faster lookup, by parameter name + + + + + Caches all effects + + + + + Caches all enumerated effect parameters + + + + + Gets the specified technique from the specified effect, or null if no technique by that name exists + + The effect + The name of the technique + The technique requested, or null if not found + + + + Caches an effect's parameters + + The effect to cache + Whether or not shared parameters should be cached + + + + Caches an model's effects and effect parameters + + The model to cache + Whether or not shared parameters should be cached + + + + Adds an effect to the cache + + The effect to cache + + + + Recreates the cache + + + + + Validates the cached model/effect to make sure it hasn't changed + + Whether or not the cache should be updated if found invalid + Whether or not the cache was valid + + + + Whether or not shared parameters should be cached + + + + + Gets a list of cached effects + + + + + Gets a list of parameters of the specified name + + The name of the parameters to retrieve + A list of parameters (or null if name not found) + + + + Retrieves all parameters of one of the standard types + + The name of the parameter + A list of parameters (or null if shared params aren't cached) + + + + Gets a parameter in a specified effect + + The effect + The parameter name + The parameter, or null if either effect or parameter not found + + + + A container for an effect that maintains variable values + + + + + The effect managed by this effect container + + + + + Creates an effect container + + The path to the effect file to use in this container + + + + Creates an effect container + + The path to the effect file to use in this container + The name of the content manager to use to load this effect + + + + Creates an effect container + + The effect to use in this container + + + + Sets all the parameters in addition to the ones in the effect itself. + + + + + Private members + + + + + Constructor + + + + + Builds the precedence table, determines + the effect + + + + + Sets the current technique to the one named. + + + + + + Gets the internal effect + + Should the parameters be set + The effect for this GenericEffect + + + + Returns a technique based on the index provided. + Will set the parameters for this effect; + + The index into the Technique List + Should we set the parameters + The EffectTechnique requested + + + + Get the current technique + + Should we set the parameters this call + The current effect + + + + Determine's which of the four shaders this instance will use + + + + + Set the parameters for Basic Shader + + + + + Sets the parameters for the dual texture effect + + + + + Sets the parameters for the environment map effect + + + + + Sets the parameters for the skinned effect + + + + + Pass common information to the graphics device + + + + + Destroy this object + + + + + Needs to be implemented by the IEffectLight child + + + + + Properties inherited from the interfaces + + + + + Class properties + + + + + The texture loading color key + + + + + Set to true to suspend device reset while loading from file + + + + + Sets the resolution + + The new width + The new height + + + + Sets the display mode to full-screen and sets the resolution + + The new width + The new height + + + + Suspends the device reset when options are changed + + + + + Resumes the device reset when options are changed + + + + + Resets the device + + + + + Sets the presentation parameters + + The structure to set parameters in + + + + Save the graphics options to a file + + The file name of the graphics options file + + + + Load the graphics options from file + + The file name of the graphics options file + + + + Gets or sets the current texture filter + + + + + Sets the width of the backbuffer and resets the device + Use SetResolution() to set both width and height simultaneously + + + + + Sets the height of the backbuffer and resets the device + Use SetResolution() to set both width and height simultaneously + + + + + Gets or sets the background color of all cameras + + + + + Sets the display mode to full screen + Use SetFullScreen() to set the full-screen resolution and full-screen simultaneously + + + + + Enables or disables multisampling + + + + + Provides an interface for objects which can be have their appearance + modified by alpha and color values using a variety of operations. + + + + + The alpha value to use with the BlendOperation. + + + + + The red value to use with the ColorOperation. + + + + + The green value to use with the ColorOperation. + + + + + The blue value to use with the color operation. + + + + + The rate of change of the alpha component in units per second. A negative value will make the object disappear over time. + + + + + The rate of change of the red component in units per second. + + + + + The rate of change of the green component in units per second. + + + + + The rate of change of the blue component in units per second. + + + + + The color operation to perform using the color component values and + Texture (if available). + + + + + The blend operation to perform using the alpha component value and + Texture (if available). + + + + Provides a Destroy method, generally used by Glue generated classes + + + + Interface for an object which has visibility control. + + + + + Layers are objects which can contain other graphical objects for drawing. Layers + are used to create specific ordering and can be used to override depth buffer and + z-sorted ordering. + + + + + List of Sprites that belong to this layer. Sprites should be added + through SpriteManager.AddToLayer or the AddSprite overloads which + include a Layer argument. + + + + + Used by the Renderer to override the camera's FieldOfView + when drawing the layer. This can be used to give each layer + a different field of view. + + + + + The Batches referenced by and drawn on the Layer. + + + The Layer stores a regular IDrawableBatch PositionedObjectList + internally. Since this internal list is used for drawing + the layer the engine sorts it every frame. + + For efficiency purposes the internal IDrawableBatch PositionedObjectList + cannot be sorted. + + + + + The Sprites referenced by and drawn on the Layer. + + + The Layer stores a regular SpriteList internally. Since + this internal list is used for drawing the layer the engine + sorts it every frame. + + For efficiency purposes the internal SpriteList cannot be sorted. + + + + + The Texts referenced by and drawn on the Layer. + + + The Layer stores a regular Text PositionedObjectList + internally. Since this internal list is used for drawing + the layer the engine sorts it every frame. + + For efficiency purposes the internal Text PositionedObjectList + cannot be sorted. + + + + + Whether the SpriteLayer is visible. + + + This does not set the contained Sprite's visible value to false. + + + + + The Models referenced by and drawn on the Layer. + + + The Layer stores a regular PositionedModel PositionedObjectList + internally. Since this internal list is used for drawing + the layer the engine sorts it every frame. + + For efficiency purposes the internal PositionedModel PositionedObjectList + cannot be sorted. + + + + + Defines an ambient light in a scene + + + + + Base class for positioned lights + + + + + Interface to lights + + + + + Gets or sets the color of this light + + + + + Gets or sets the color of this light + + + + + Gets or sets the enabled status of this light + + + + + The color of this light. The default value is (1,1,1). + + + + + The color of this light. The default value is (0,0,0). + + + + + Whether or not this light is enabled + + + + + This is mostly for the Windows Phone system + All lights on the phone are directional, relative to the object being lit. + + + + + + + Gets or sets the RGB color of this light + + + + + Gets or sets the RGB color of this light + + + + + Gets or sets whether or not this light is enabled + + + + + A light which containing direction and color information. This light has no bounds, limits, or source, + so all objects which can be lit will be lit by directional lights equally. + + + + + This is mostly for the Windows Phone system + All lights on the phone are directional, relative to the object being lit. + Overloaded for directional lights since they always effect all models the same. + + + + + + + Provides a method of indexing lights without access to the actual lists + + The type of lights + + + + Sets the lighting to one of the default lighting setups + + The lighting setup to use + + + + Gets or sets the name of the lighting collection (e.g. Evening) + + + + + Creates a point light, based on position and color, and adds it to the manager + + + + + + + + Creates a point light, does not add the light to the manager + + + + + + + + Creates a directional light, based on direction and color, and adds it to the manager + + + + + + + + Creates a directional light, does not add the light to the manager + + + + + + + + Runtime effect property value which can be used to set effects. + + + + + A list of effects and parameters. + + + This class is generally not used during runtime - it's used in the FlatRedBall content pipeline. + + + + + A bone that can be attached to + + + + + An interface for objects which can be scaled on the X, Y, and Z axes. + + + + + Represents an object that can be scaled on the X and Y axes and can have + scale velocity on these two axes. + + + + + Represents an object which has read-only scale values on two axes. + + + + + Gets the X Scale of the object. + + + + + Gets the Y Scale of the object. + + + + + Gets and sets the X Scale of the object. + + + + + Gets and sets the Y Scale of the object + + + + + Gets and sets the rate at which the X Scale of the object changes in units per second. + + + + + Gets and sets the rate at which the Y Scale of the object changes in units per second. + + + + + Create a new positioned model without adding to engine + + + + + Provides access to the BasicModelEffect. This should only be used in debugging. + + + + + Provides access to positioned bones in a model + + + + + Detaches all bones from parents/children and destroys the collection + + + + + Creates a positioned bone when asking for one that does not exist yet + + The name of the bone to get + A positioned bone or animated positioned bone + + + + Gets the positioned bone with the specified name + + The name of the bone + A positioned bone + + + + Contains information about what fields a model's mesh's vertices provide + + + + + Defines an interface for objects which can be selectable by the cursor. + + + + + Whether the instance is currently selectable (active). + + + + + Specifies the ordering and distance of meshes to be used at different LODs + + + + + Whether animation is being performed. + + + + + Specifies a custom effect to use with this model + (when null, uses Model's internal effects) + + + + + A list of vertex information, indexed as VertexInformation[mesh#][meshpart#] + Built during ModelManager.AddModel() + + + + + Creates a new PositionedModel which shares the same reference + for XnaModel and Effects. + + The newly-created PositionedModel. + + + + Whether or not this model has animation information + + + + + Sets the model's texture + Will not work if the model is using custom effects or if + the model loads its own fx files unless you are using + the same parameter name for texture as BasicEffect uses. + + + + + Gets or sets the type of LOD drawing to use for this model + + + + + Gets the transformation (world) matrix for this model + + + + + The index of the mesh in the model's mesh collection + + + + + The distance after which this mesh will be drawn (up until the next + mesh's distance, if it exists) + + + + + A list of IAttachables which is by default two-way. + + Type of the list which is of IAttachable. + + + + Interface defining that an object has a RemoveObject method. This standardizes the way that objects remove themselves from + two-way lists. + + + This should not be implemented outside of the FlatRedBall Engine, but is public so that PositionedOjbect-inheriting + objects can access the lists of objects that they belong to. + + + + + Creates a new AttachableList. + + + + + Creates a new AttachableList with the argument capacity. + + The initial capacity of the new AttachableList. + + + + Returns the top parents in the argument AttachableList + + The type of object in the returned list. + Tye type of object in the argument list + The list to search through. + List of T's that are the top parents of the objects in the argument AttachableList. + + + + Adds the argument to the AttachableList and creates a two-way relationship. + + The IAttachable to add. + + + + Adds all IAttachables contained in the argument AttachableList to this AttachableList and creates two + way relationships. + + + + + + Adds the argument attachable to this without creating a two-way relationship. + + The IAttachable to add to this. + + + + Adds all IAttachables contained in the argument AttachableList to this + without creating two-way relationships. + + The list of IAttachables to add. + + + + Adds a new IAttachable if it is not already in the list. + + The IAttachable to add. + Index where the IAttachable was added. -1 is returned if the list already contains the argument attachable + + + + Adds the argument IAttachable to this and creates a two-way relationship if + this does not already contain the IAttachable. + + The IAttachable to add. + + + + Removes all IAttachables contained in this and eliminates all + two-way relationships. + + + + + Returns whether this contains the argument IAttachable. + + + If the argument is part of this instance and the two share a + two-way relationship then this method is able to use this two-way + relationship to speed up the method call. + + The argument IAttachable to search for. + Whether the argument attachable is contained in this list. + + + + Returns the IAttachable with name matching the argument. + + This method performs a case-sensitive search. + The name to match when searching. + The IAttachable with matching name or null if none are found. + + + + Returns the first IAttachable with a name containing the argument string. + + This method returns any IAttachable that has a name that contains the argument. + For example, an object with the name "MySprite" would return if the argument was "Sprite". + The string to check IAttachables for. + The IAttachable with a name containing the argument string or null if none are found. + + + + Returns the first IAttachable with a name containing the argument string, case insensitive. + + This method returns any IAttachable that has a name that contains the argument. + For example, an object with the name "MySprite" would return if the argument was "Sprite". + The string to check IAttachables for. + The IAttachable with a name containing the argument string or null if none are found. + + + + Inserts the argument IAttachable at the argument index and creates a + two-way relationship. + + The index to insert at. + The IAttachable to insert. + + + + Inserts the argument IAttachable at the argument index but does not create + a two-way relationship. + + The index to insert at. + The IAttachable to insert. + + + + Breaks all two-way relationships between this and all contained + IAttachables. + + + This will still contain the same number of IAttachables before and + after the call. + + + + + Makes the relationship between all contained IAttachables and this a two way relationship. + + + If an IAttachable is added (through the Add method), the relationship is already a + two-way relationship. IAttachables which already have two-way relationships will not be affected + by this call. IAttachables that have been added through the AddOneWay call or added + through a call that returns a one-way array will be modified so that they hold a reference to + this instance in their ListsBelongingTo field. One-way relationships are often created in + FRB methods which return AttachableLists. + + + + + Moves the position of a block of IAttachables beginning at the argument + sourceIndex of numberToMove count to the argument destinationIndex. + + The index of the first IAttachable in the block. + The number of elements in the block. + The index to insert the block at. + + + + Removes the argument IAttachable from this and clears the two-way relationship. + + The IAttachable to remove from this. + + + + Removes all IAttachables contained in the argument attachableList from this and clears the two-way relationships between + this and all IAttachables removed. + + The list of IAttachables to remove. + + + + Removes the IAttachable at the argument index and clears two-way relationships. + + The index of the object to remove. + + + + Removes the IAttachable at the argument index from the list, but the IAttachable will continue to reference + this List in its ListsBelongingTo. + + + + + + Returns a string with the name and the number of elements that this contains. + + The string with this instance's name and element count. + + + + The number of elements contained in the list. + + + + + Gets and sets the name of this instance. + + + + + Gets the first object found after the argument "value" on the argument "axis". Lists + must be sorted for this method to work effectively. WARNING: This method uses an inclusive upper bound. Use GetFirstAfter instead which uses an exclusive upper bound. + + The value to search after. For example, this method will return objects with their position values greater than + the argument value. In other words, if 0 is passed as the value, then objects with position values greater than (not equal to) will be returned. + The axis representing the value to use (x, y, or z) + The lower (inclusive) bound. + The upper (inclusive) bound. This argument is why GetFirstAfterPosition is obsolete. + The index of the first object after the given value. + + + + Gets the first object found after the argument "value" on the argument "axis". Lists + must be sorted ascending for this method to work effectively. + + + This method is useful when searching for items in a list after a given value. + + The value to search after. For example, this method will return objects with their position values greater than + the argument value. In other words, if 0 is passed as the value, then objects with position values greater than (not equal to) will be returned. + The axis representing the value to use (x, y, or z) + The lower (inclusive) bound. + The upper (exclusive) bound. + The index of the first object after the given value. + + + + Returns a one-way List containing the TopParents of all items in the list without duplication. + + The list of parents. + + + + Shifts all contained objects' Position by the arguments x,y,z. + + Amount to move on the X axis. + Amount to move on the Y axis. + Amount to move on the Z axis. + + + + Shifts all contained objects' Position by the argument vector. + + The amount to change Position by. + + + + Shifts all contained objects' RelativePosition by the argument vector. + + The amount to change RelativePosition by. + + + + This template contains animations referencing a previous frame. It should contain one reference + to a frame and at least one set of AnimationKeys. It also can contain an AnimationOptions data object. + + + + + Used as the base class that binds all template types together. + + + + + universally unique identifier (UUID) formatted to the Open Software Foundation's Distributed + Computing Environment standard and surrounded by angle brackets < and > + + + + + This template contains animations referencing a previous frame. It should contain one reference + to a frame and at least one set of AnimationKeys. It also can contain an AnimationOptions data object. + + + + + Returns a string representation of this template. + + + + + This template defines a set of animation keys. The keyType member specifies whether the keys are rotation, + scale or position keys (using the integers 0, 1, or 2 respectively). + + + + + This template defines a set of animation keys. The keyType member specifies whether the keys are rotation, + scale or position keys (using the integers 0, 1, or 2 respectively). + + + + + Returns a string representation of this template. + + + + + The Type of Key by an integer value. + 0 - Rotation + 1 - Scale + 2 - Position + + + + + An integer value for the amount of Keys. + + + + + A TimedFloatKeys array for ths AnimationKey. + + + + + This template enables you to set the Direct3D Retained Mode Animation options. + The openclosed member can be either 0 for a closed or 1 for an open animation. + The positionquality member is used to set the position quality for any position keys specified and can either be 0 + for spline positions or 1 for linear positions. By default, an animation is closed. + + + + + This template enables you to set the Direct3D Retained Mode Animation options. + The openclosed member can be either 0 for a closed or 1 for an open animation. + The positionquality member is used to set the position quality for any position keys specified and can either be 0 + for spline positions or 1 for linear positions. By default, an animation is closed. + + + + + Returns a string representation of this template. + + + + + An integer value for whether or not the animation is open or closed. + + + + + An integer value for whether or not the animation using spline or linear positions for + interpolation. + + + + + An AnimationSet template contains one or more Animation objects and is the equivalent to the Direct3D Retained Mode + concept of animation sets. This means each animation within an animation set has the same time at any given point. + Increasing the animation set's time will increase the time for all the animations it contains. + + + + + An AnimationSet template contains one or more Animation objects and is the equivalent to the Direct3D Retained Mode + concept of animation sets. This means each animation within an animation set has the same time at any given point. + Increasing the animation set's time will increase the time for all the animations it contains. + + + + + Returns a string representation of this template. + + + + + Defines a simple Boolean type. This template should be set to 0 or 1. + + + + + A private value which is used to keep the TrueFalse within the 0 - 1 range. + + + + + Defines a simple Boolean type. This template should be set to 0 or 1. + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA bool representation of this Boolean + + The Boolean to convert. + + + + An integer value to represent True or False. + 1 = True, 0 = False. + + + + + This template defines a set of two Boolean values used in the MeshFaceWraps template to + define the texture topology of an individual face. + + + + + This template defines a set of two Boolean values used in the MeshFaceWraps template to + define the texture topology of an individual face. + + + + + Returns a string representation of this template. + + + + + A boolean value for the 2 dimensional U value + + + + + A boolean value for the 2 dimensional V value + + + + + This template defines the basic RGB color object. + + + + + This template defines the basic RGB color object. + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Color representation of this ColorRGB + + The ColorRGB to convert. + + + + Returns a Microsoft XNA Vector4 representation of this ColorRGB + + The ColorRGB to convert. + + + + Returns a Microsoft XNA Vector3 representation of this ColorRGB + + The ColorRGB to convert. + + + + A Float value that represents the Red amount. + + + + + A Float value that represents the Green amount. + + + + + A Float value that represents the Blue amount. + + + + + This template defines a color object with an alpha component. + This is used for the face color in the material template definition. + + + + + This template defines a color object with an alpha component. + This is used for the face color in the material template definition. + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Color representation of this ColorRGBA + + The ColorRGBA to convert. + + + + Returns a Microsoft XNA Vector4 representation of this ColorRGBA + + The ColorRGBA to convert. + + + + Returns a Microsoft XNA Vector3 representation of this ColorRGBA + + The ColorRGBA to convert. + + + + A Float value that represents the Red amount. + + + + + A Float value that represents the Green amount. + + + + + A Float value that represents the Blue amount. + + + + + A Float value that represents the Alpha amount. + + + + + A two dimensional vector used to define a mesh's texture coordinates. + + + + + A two dimensional vector used to define a mesh's texture coordinates. + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Vector3 representation of this Vector + + The Coords2d to convert + + + + The U Coorinate for Texture Coordinates + + + + + The V Coorinate for Texture Coordinates + + + + + This template defines an array of floating-point numbers (floats) and the number of floats in that array. + This is used for defining sets of animation keys. + + + + + This template defines an array of floating-point numbers (floats) and the number of floats in that array. + This is used for defining sets of animation keys. + + + + + Returns a string representation of this template. + + + + + A integer value for the amount of Float values. + + + + + A float array of the values for the Float Keys. + + + + + This template defines a frame. Currently, the frame can contain objects of the type Mesh and a FrameTransformMatrix. + + + + + This template defines a frame. Currently, the frame can contain objects of the type Mesh and a FrameTransformMatrix. + + + + + Returns a string representation of this template. + + + + + The Name for this Frame. + + + + + This template defines a local transform for a frame (and all its child objects). + + + + + This template defines a local transform for a frame (and all its child objects). + + + + + Returns a string representation of this template. + + + + + A Matrix4x4 value for the Frame. + + + + + This template defines the application-specific header for the Direct3D Retained Mode usage of the DirectX file format. + The Retained Mode uses the major and minor flags to specify the current major and minor versions for the Retained Mode + file format. + + + + + This template defines the application-specific header for the Direct3D Retained Mode usage of the DirectX file format. + The Retained Mode uses the major and minor flags to specify the current major and minor versions for the Retained Mode + file format. + + + + + Returns a string representation of this template. + + + + + Major Version of this File Format + + + + + Minor Version of this File Format + + + + + 32 or 64 Bit Flag + + + + + This template consists of an index parameter and an RGBA color and is used for defining mesh vertex colors. + The index defines the vertex to which the color is applied. + + + + + This template consists of an index parameter and an RGBA color and is used for defining mesh vertex colors. + The index defines the vertex to which the color is applied. + + + + + Returns a string representation of this template. + + + + + An integer value for the Index of this color. + + + + + The ColorRGBA value for this IndexedColor + + + + + This template defines a basic material color that can be applied to either a complete mesh or a mesh's individual faces. + The power is the specular exponent of the material. Note that the ambient color requires an alpha component. + TextureFilename is an optional data object used by Direct3D Retained Mode. If this object is not present, + the face is untextured. + + + + + This template defines a basic material color that can be applied to either a complete mesh or a mesh's individual faces. + The power is the specular exponent of the material. Note that the ambient color requires an alpha component. + TextureFilename is an optional data object used by Direct3D Retained Mode. If this object is not present, + the face is untextured. + + + + + Returns a string representation of this template. + + + + + The Name of this Material. + + + + + A ColorRGBA value for the Diffuse Color + + + + + A Float value for the Specular Power. + + + + + A ColorRGB value for the Specular Color + + + + + A ColorRGB value for the Emissive Color + + + + + This template defines a 4×4 matrix. This is used as a frame transformation matrix. + + + + + This template defines a 4×4 matrix. This is used as a frame transformation matrix. + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Matrix representation of this Matrix4x4 + + The Matrix4x4 to convert. + + + + A float array used as the matrix. + + + + + This template defines a simple mesh. The first array is a list of vertices, and the second array defines the faces + of the mesh by indexing into the vertex array. + + + + + This template defines a simple mesh. The first array is a list of vertices, and the second array defines the faces + of the mesh by indexing into the vertex array. + + + + + Returns a string representation of this template. + + + + + The Name of this Mesh. + + + + + An integer value of Verticies for this Mesh. + + + + + A Vector array of verticies for this Mesh. + + + + + An integer value of Faces for this Mesh. + + + + + A MeshFace array of Faces for this Mesh. + + + + + This template is used by the Mesh template to define a mesh's faces. Each element of the nFaceVertexIndices + array references a mesh vertex used to build the face + + + + + This template is used by the Mesh template to define a mesh's faces. Each element of the nFaceVertexIndices + array references a mesh vertex used to build the face + + + + + Returns a string representation of this template. + + + + + Mesh face Vertex Indicies Count + + + + + An int array of the Mesh Face Indicies + + + + + This template is used to define the texture topology of each face in a wrap. + The value of the nFaceWrapValues member should be equal to the number of faces in a mesh. + + + + + This template is used to define the texture topology of each face in a wrap. + The value of the nFaceWrapValues member should be equal to the number of faces in a mesh. + + + + + Returns a string representation of this template. + + + + + An integer value for the value of this MeshFaceWraps + + + + + A Boolean2d value for this MeshFaceWrap values. + + + + + This template is used in a mesh object to specify which material applies to which faces. The nMaterials member + specifies how many materials are present, and materials specify which material to apply. + + + + + This template is used in a mesh object to specify which material applies to which faces. The nMaterials member + specifies how many materials are present, and materials specify which material to apply. + + + + + Returns a string representation of this template. + + + + + A integer value for the amount of Matrials for this Mesh. + + + + + A integer value of the amount of faces. + + + + + A integer array of the Faces. + + + + + This template defines normals for a mesh. The first array of vectors is the normal vectors themselves, + and the second array is an array of indexes specifying which normals should be applied to a given face. + The value of the nFaceNormals member should be equal to the number of faces in a mesh. + + + + + This template defines normals for a mesh. The first array of vectors is the normal vectors themselves, + and the second array is an array of indexes specifying which normals should be applied to a given face. + The value of the nFaceNormals member should be equal to the number of faces in a mesh. + + + + + Returns a string representation of this template. + + + + + A integer value of the amount of Normals for this Mesh. + + + + + A Vector array of the Normals for this mesh. + + + + + An integer value of the Amount of Face Normals. + + + + + A Mesh Face array of the Face Normals. + + + + + This template defines a mesh's texture coordinates + + + + + This template defines a mesh's texture coordinates + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Vector2 List representation of this MeshTextureCoords + + The MeshTextureCoords to convert. + + + + Returns a Microsoft XNA Vector2 array representation of this MeshTextureCoords + + The MeshTextureCoords to convert. + + + + An integer value of the Total amount of Texture Coordinates. + + + + + A Coords2d array of TextureCoords + + + + + This template specifies vertex colors for a mesh, instead of applying a material per face or per mesh. + + + + + This template specifies vertex colors for a mesh, instead of applying a material per face or per mesh. + + + + + Returns a string representation of this template. + + + + + Amount of VertexColors + + + + + An Array of IndexedColors. + + + + + Currently unused. + + + + + Currently unused. + + + + + Returns a string representation of this template. + + + + + An angle value for this Quanternion + + + + + A 3d vector for this Quanterion + + + + + This template allows you to specify the file name of a texture to apply to a mesh or a face. + This template should appear within a material object. + + + + + This template allows you to specify the file name of a texture to apply to a mesh or a face. + This template should appear within a material object. + + + + + Returns a string representation of this template. + + + + + A string representation of where to load the texture from. + + + + + This template defines a set of floats and a positive time used in animations. + + + + + This template defines a set of floats and a positive time used in animations. + + + + + Returns a string representation of this template. + + + + + A integer value for the time of this TimedFloatKeys + + + + + FloatKeys for this timed Float Key. + + + + + This template defines a vector + + + + + This template defines a vector + + + + + Returns a string representation of this template. + + + + + Returns a Microsoft XNA Vector3 representation of this Vector + + The Vector to convert + + + + The X Cordinate in 3D space. + + + + + The Y Cordinate in 3D space. + + + + + The Z Cordinate in 3D space. + + + + + Defines the state of Sprites immediately after being created by the containing Emitter. + + + + + Sets the type of velocity to use. This impacts which Velocity values are + applied to emitted Sprites. + + + + + Each individual component (X, Y, Z) had an independent range. + + + + + The X and Y components are set according to a random angle spanning a full circle and a radial velocity or rate value. + + + + + The X,Y,and Z components are set according to a random point on a full sphere and a radial velocity or rate value. + + + + + The X and Y components are set according to a random angle within wedge values using a radial velocity or rate value. + + + + + The X, Y, and Z components are set according to a random point on a + + + + + An emitter is an invisible object which can create one or more Sprites at a specific + rate or on a method call. + + + + + This is used only in the ParticleEditor. + + + + + Emits particles as specified by the Emitter class + + + This method initiates one emission of particles. The number of particles emitted + depends on the numberPerEmission variable. This method does not consider emission timing, and the time + is not recorded for emission timing. + + The argument SpriteList stores all Sprites which were emitted + during the call. The Sprites are added regularly, rather than in a one way relationship. This enables + modification of emitted particles after the method ends. null can be passed as an argument + if specific action is not needed for emitted particles. Particles are automatically created + through the SpriteManager as Particle Sprites. + + + + + The list of Sprites (which can be null) to add all Sprites created + by this call. + + + + Specifies the number of seconds that particles will remain on the screen and in memory. This is only considered + if the RemovalEvent is set to RemovalEventType.TIMED; + + + + + No removal event specified. + + + + + Particles will be removed when out of the screen. + + + This uses the camera's IsSpriteInView method. + + + + + Particles will be removed when Alpha is 0 + + + Setting the Alpha to 0 manually on a particle created by an emitter with this removal event will + also remove the Sprite. + + + + + Particles will be removed after a certain amount of time has passed after emission. This value is set through the + SecondsLasting property. + + + + + List of Emitters provoding shortcut methods for interacting with all contained Emittes. + + + This is the runtime object created when loading .emix files. + + + + + Rendering modes available in FlatRedBall + + + + + Default rendering mode (uses embedded effects in models) + + + + + Color rendering mode - renders color values for a model + (does not include lighting information) + Effect technique: RenderColor + + + + + Normals rendering mode - renders normals + Effect technique: RenderNormals + + + + + Depth rendering mode - renders depth + Effect technique: RenderDepth + + + + + Position rendering mode - renders position + Effect technique: RenderPosition + + + + + Static class responsible for drawing/rendering content to the cameras on screen. + + This class is called by + + + + Ensures that a render target pair of this surface format exists + + The surface format for the render targets + + + + Draws a quad + The effect must already be started + + + + + Draws a full-screen quad + The effect must already be started + + + + + Gets the default Camera (SpriteManager.Camera) + + + + + Gets the list of cameras (SpriteManager.Cameras) + + + + Temporary Fix to resolve issues with render states while async loading + + + + The possible ways to sort objects which should be sorted (Sprites, Texts, IDrawableBatches). + + + + + Renders objects + + + + + Prepares objects on the specified camera for drawing before beginning the drawing loop. + + The camera to prepare. + + + + Sets up render states for this renderer. Called when the last renderer to draw was not + this renderer. + + + + + + + Draws objects on the specified layer. For renderers that DrawSorted, this should draw the + next object on the layer, and GetNextObjectDepth should now return the following object. + + The camera to draw. + The layer to draw. + + + + + Returns the depth of the next object to draw on this layer for the current sort mode. + + The Camera currently being drawn. + The layer currently being drawn. + The depth of the next object on this layer for the current sort mode. + + + + Returns true if this layer hasn't drawn all of its objects for this Renderer. + + The Camera to check + The layer to check. + Whether or not the renderer has drawn all objects on the layer. + + + + Called to remove layer information from a renderer (for renderers that store + layer information). + + The Camera to move the Layer from + The layer to remove + + + + Whether or not the objects in this renderer are drawn in order with order renderers. + + + + + Gets or sets the current sorting mode. + + + + + Render overrides are a way to inject new information into the BasicEffect + + + + + Resolves the texture by setting the current render target to null (back buffer) and then + getting the texture from the previously drawing render target + + + + + Sets this render target on the device + + + + + Manages a swappable pair of render targets (of the same surface format) + + + + + This switches the currently active render target. + Use to set this type of target, or switch the currently used + target of this type. + + + + + This switches the currently active render target and gets the + resolved texture from the previous target. + Use to set this type of target, or switch the currently used + target of this type. + + + + + Gets the current render target + + + + + Gets the width of this render target pair + + + + + Gets the height of this render target pair + + + + + Interface for checking mouse over's + + + + + Check to see if object is under mouse. + + Cursor to check against. + True if under mouse. + + + + Check to see if object is under mouse. + + Cursor to check against. + Layer object is on. + True if under mouse. + + + + Creates a new Text object. + + + Reference to the new Text object. The object will not + be in the TextManager's memory. + + + + + Set the RGB properties of this all at once. 0,0,0 is Black, 1,1,1 is White. + + 0 to 1 + 0 to 1 + 0 to 1 + + + + Sets the Scale and Spacing such that the Text is drawn pixel-perfect at its given Z position. + + Reference to the camera to use when calculating the Scale and Spacing. + + + + Sets the Scale and Spacing such that the Text is drawn pixel-perfect at the given Z position. + This method obeys the Layer's overridden field of view if it uses one. + + + + + + Updates the displayed text according to the MaxWidth. + + + + + Gets or sets the string that the Text object is to display. + + + + + Returns the center of the text object. + + + If the text is centered (the format.alignment equals TextManager.Alignment.CENTER), + this will simply return the x value of the text. Otherwise, this property + calculates the center of the text based on the contained string, the format, whether + the text uses 3D fonts, and the x position of the text. + + + + + The rate of change of the alpha component in units per second. + + + + + Returns the number of lines in the text object. + + + This currently reports the number of lines only when the text + object is using a bitmap font. 3D text does not use the newline + character. + + + + + The maximum width of the text in world units. This modifies the DisplayedText property. + + + + + Returns the distance from the center of the Text object to the edge; + + + + + Returns the vertical center of the text. + + + Since the y value of text marks either the top or bottom of the Text + depending on vertical alignment, this value can be useful for finding + the Text's center. + + + + + The ContentManager used to store + textures generated by the Text. This + is required for proper cleanup. + + + + + Creates a new Text with the argument string which will only be drawn in the argument Camera's + destination. + + + The new text will be stored both by the camera and the TextManager's managed invisible Texts array. + The text is automatically attached to the argument Camera so to be moved, it must either + be detached or moved with relative variables. + + The string to show. + The camera that the newly created Text belongs to. + Reference to the newly created Text. + + + + Adds an already-created Text instance to the argument camera. + + + The argument Text should not already be in the TextManager's memory when this + method is called. + + Reference to the text object to add. + Reference to the camera to be added to. + The added text. + + + + Returns the width of the rendered text assuming the spacing is 1. + + + If there are newline characters in the string, GetWidth returns the width of the longest line. + Spaces are considered characters as well so "Hello " will be longer than "Hello". + + The text to measure. + The width of the longest line in the text. + + + + A list of Bitmaps which can be created from a loaded .gif. + + + This class can be created through the FlatRedBallServices.Load method. + + + + + Removes the index row from the contained data. Row 0 is the top of the texture. + + The index of the row to remove. Index 0 is the top row. + + + + The relative X position. + + + + + The relative Y position. + + + + + The cursor is a controllable graphical icon which can interact with FRB elements and + stores information about mouse activity. + + + + + Controls how sensitive the cursor is to mouse movements. + + + Default value is 1, so setting the value to 2 will make the mouse + twice as sensitive. + + + + + The Sprite that the cursor uses to draw itself. + + + This field can be modified to change how the Sprite is drawn. The following Sprite values modify the cursor: + + x - relative to the camera + y - relative to the camera + ScaleX + ScaleY + RotationX + RotationY + RotationZ + texture + fade + visible + + Other values will not modify how the Sprite is drawn. + + + + + + The x distance from the center of the Cursor which is used as the cursor's tip for selection. + + + + + The y distance from the center of the Cursor which is used as the cursor's tip for selection. + + + + + The window that the cursor was over when the mouse button was pressed. + + + When the secondary (right) mouse button is pushed down, this value is set - either to null + if the mouse is not over any Windows or to the Window that the mouse is over. + When the secondary mouse button is released (clicked), this value is set to null. + This value is useful for clicking on Windows. Specifically, when the cursor is clicked + on a Button, the WindowPushed value is tested to make sure that it is the same as + the Window clicked on. This allows for players to push on a Button but move + the mouse away and click elsewhere without clicking on the Button originally + pushed and without accidentally clicking on other Buttons. This is also used + with ToggleButtons to control when they are pressed and unpressed. + + + + + If this value is true, the Cursor will not move in response to the mouse or gamepad. + + + This value can be set to true if the Cursor should not move in response to input. + The staticPosition value is only used by the engine when over the button on an UpDown. When pushing down + on an UpDown button, the staticPosition is set to true, and set to false when releasing the mouse button. + + + + + The window that the cursor has grabbed. + + + When the mouse button is released, the windowGrabbed reference is set to null. If a Window is grabbed, it will + move as the mouse moves. This is used commonly for dragging on menu bars and scroll bars. The cursor does + not recognize which types of Windows can be dragged, so windows must be grabbed through the Cursor.GrabWindow + method. The windowGrabbed's onDrag event is fired every frame. + + + + + Storage for reference to a grabbed object. + + + This variable has no internal engine functionality. It merely provies a place to store a reference + to a grabbed Object - useful in graphical applications where the Cursor can grab and move objects such + as Sprites or Text objects. + The ObjectGrabbedRelativeX and ObjectGrabbedRelativeY can also be set to keep the object static after a click rather than + "snapping" its center to the Cursor's tip. + + + + + + The relative x position of a grabbed object from the center of the cursor. + + + This value can be set through the SetObjectRelativePosition. + This value used in the GetCursorPositionForSprite method. + + + + + + + The relative y position of a grabbed object from the center of the cursor. + + + This value can be set through the SetObjectRelativePosition. + This value used in the GetCursorPositionForSprite method. + + + + + + + Determines whether the primary button was pushed this frame. + + + + + Determines whether the primary button is down this frame. + + + + + Determines whether the primary button was clicked (released) this frame. + + + + + Determines whether the primary button was double clicked this frame. + + + + + Determines whether the secondary button was pushed this frame. + + + + + Determines whether the secondary button is down this frame. + + + + + Determines whether the secondary button was clicked (released) this frame. + + + + + Determines whether the secondary button was double clicked this frame. + + + + + Reference to the camera to which the cursor belongs. + + + + + Creates a new Cursor + + + Usually the cursor does not have to be created explicitly. Calling + the GuiManager's constructor without a cursor reference (default in the + FrbTemplate) creates a cursor automatically. New cursors should only + be created if multiple cursors are needed, or if you are inheriting + from the Cursor class. Usually multiple cursors are only needed + in games with multiple cameras. + + The camera that the cursor will belong to. + + + + Returns the xVelocity of the cursor at a particular Z. + + + As the cursor can control objects at various Z values, this method returns the X velocity + of the cursor at a paritular Z. This method assumes that the Camera is unrotated (looking + down the Z axis. + + The z value to measure the velocity at. + The X velocity at the particular Z. + + + + Returns the yVelocity of the cursor at a particular Z. + + + As the cursor can control objects at various Z values, this method returns the Y velocity + of the cursor at a paritular Z. This method assumes that the Camera is unrotated (looking + down the Z axis. + + The z value to measure the velocity at. + The Y velocity at the particular Z. + + + + Modifies the x and y arguments to show the point of the cursor's tip at at the z value. + + + This method assumes that the Camera is unrotated (looking + down the Z axis). + + The x value to change. + The y value to change. + The z value to use. + + + + Modifies the x and y arguments to show the position that the grabbed Sprite should be at. + + + This method assumes that the camera's currentFollowingStyle is Camera.LOOKINGSTYLE.DOWNZ. The x and y + arguments will represent the location of the cursor's tip and adds the relative Sprite positions. + + + + + + + + + + Gets the cursor's position and sets the argument positionedObject's + x and y values to the cursor's position at the argument Z value. + + + This is a simple way to set the position of an object + to the cursor's position. + + Reference to the positioned object. + The Z value at which the position should be set. + + + + Returns the Sprite the cursor is over giving preference to the closest Sprite to the Camera. + + + This method will not return inactive Sprites (Sprites with the .active variable set to false). To consider inactives as well + call the GetSpriteOver(SpriteArray, bool) overload. + + The SpriteArray to search through. + The Sprite that is found, or null if the cursor is not over any Sprites. + + + + Returns the Sprite the cursor is over giving preference to the closest Sprite to the Camera. + + The SpriteArray to search through. + Whether inactive Sprites (Sprites with the .active variable set to false) are considered. + + The Sprite that is found, or null if the cursor is not over any Sprites. + + + + Returns the Sprite the cursor is over giving preference to the closest Sprite to the Camera. + + The SpriteArrayArray to search through. + The Sprite that is found, or null if the cursor is not over any Sprites. + + + + Returns the Sprite the cursor is over giving preference to the closest Sprite to the Camera. + + The SpriteLayer to search through. + The Sprite that is found, or null if the Cursor is not over any Sprites. + + + + Returns the Sprite that the cursor is over in the argument SpriteGridArray. + + Reference to the SpriteGridArray. + The Sprite that the cursor is over. + + + + Grabs a Window with the Cursor. + + + The windowGrabbed reference will automatically be set to null when the primary button is + released (clicked) by the GuiManager.Control method. + + + The Window to grab. + + + + Determines whether the cursor is on a Sprite, but only considers rotation on the z axis (RotationZ). + + + This method will not consider whether spriteToTest is rotated on the x or y axes, and assumes + that the camera is looking down the Z axis (the Camera is unrotated). Cursor.IsOn3D + works properly for any rotation.. + + This method will not select Sprites which are closer that the camera's nearClipPlane or + further than the camera's farClipPlane. + + The Sprite to test if the cursor is over. + Whether the cursor is on the Sprite. + + + + Determines whether the cursor is over a textObject. + + + This method will not consider whether the Text is rotated on the x or y axes, and assumes + that the cursor is looking down the Z axis(the camera.LookStyle is DOWNZ). + + This method will not select Texts which are closer that the camera's nearClipPlane or + further than the camera's farClipPlane. + + Currently, the method treats the text object as one rectangle, so the width of the Text will be equal + to the widest line. For example: + + + // if the text were centered, the collidable area would be as follows + +-------------------------+ + | The outline | + |represents the collidable| + | area of the Text. | + | Notice that the border | + | extends to include | + | the longest line. | + +-------------------------+ + + // if the text were left aligned, the area would still be the same: + +-------------------------+ + |The outline | + |represents the collidable| + |area of the Text. | + |Notice that the border | + |extends to include | + |the longest line. | + +-------------------------+ + + + The TextObject to test if the cursor is over. + Whether the cursor is on the TextObject. + + + + Returns whether the cursor is over the argument windowToTest. + + + This method will work accurately for both GuiManager-drawn Windows + and SpriteFrame Windows. + + Reference to the window to test. + Whether the cursor is over the argument Window. + + + + Returns whether the cursor is over the argument SpriteFrame. + + Reference to the SpriteFrame to test. + Whether the cursor is over the argument SpriteFrame. + + + + Determines whether the Cursor is on a Sprite. + + + This is the "full featured" version of the IsOn method, considering rotation on all axes for + both the spriteToTest and the camera. All lookingStyles are considered and will return accurate results. + This method is slower than the IsOn method, and if Sprites are facing the camera under its + default orientation, the IsOn method should be used. + + This method will not select Sprites if the cursor is over them on a point + that is closer that the camera's nearClipPlane or further than the camera's + farClipPlane. + + + The Sprite to test if the Cursor is over. + Whether the Cursor is over the Sprite. + + + + Determines whether the Cursor is on a Sprite and stores the intersection point in a Vector3. + + + This is the "full featured" version of the IsOn method, considering rotation on all axes for + both the spriteToTest and the camera. All lookingStyles are considered and will return accurate results. + This method is slower than the IsOn method, and if Sprites are facing the camera under its + default orientation, the IsOn method should be used. + + This method also modifies a Vector3 which marks the intersection point on the Sprite. + + This method will not select Sprites if the cursor is over them on a point + that is closer that the camera's nearClipPlane or further than the camera's + farClipPlane. + + The Sprite to test if the Cursor is over. + The point where the cursor touches the Sprite. + Whether the Cursor is on the Sprite. + + + + Checks to see if the Cursor's texture matches the passed argument. + + The texture to compare to. + Whether the argument is the same as the Cursor's texture. + + + + Determines whether the cursor is currently in the active Control which owns the application. + + Whether the cursor is currently in the active Control which owns the application. + + + + Sets the texture, ScaleX and ScaleY, and the tip offsets. + + + This method just sets the properties in one call. The individual properties can + also be set individually. + + Texture to use. + The ScaleX to set. ScaleY will be set according to the source texture's dimensions. + The relative x value of the tip of the Cursor from its center. + the relative y value of the tip of the Cursor from its center. + + + + Sets the cursor to be controlled by the joystick rather than the mouse. + + Refernce to the joystick that will control the cursor. + + + + Tells the Cursor to store the relative position of the Sprite to the Cursor's tip. + + + The relative Sprite position is used in the GetCursorPositionForSprite method. Relative values + keep objects from "snapping" to the center of the cursor when grabbed. + + The PositionedObject that the relative values should be set to. + + + + Sets whether the cursor is active. + + + An inactive cursor will not move or read any input. + Setting the active property to false will also clear the following + fields: + + primaryClick + primaryDoubleClick + primaryDown + primaryPush + secondaryClick + secondaryDoubleClick + secondaryDown + secondaryPush + + + + + + The cursor's X position relative to the camera at 100 units away from the camera. + + + As objects can have varying distances away from the camera, when trying to move + objects with the cursor, it is usually not accurate to say: + + someObject.X = cursor.X; + someObject.Y = cursor.Y; + + This will only be accurate if the object is also 100 units away from the camera and + if the camera is centered on the origin. The + x and y values must be passed to the GetCursorPosition method. + + + + + + The cursor's Y position relative to the camera at 100 units away from the camera. + + + As objects can have varying distances away from the camera, when trying to move + objects with the cursor, it is usually not accurate to say: + + someObject.X = cursor.X; + someObject.Y = cursor.Y; + + This will only be accurate if the object is also 100 units away from the camera and + if the camera is centered on the origin. The + x and y values must be passed to the GetCursorPosition method. + + + + + The change in x since last frame in world units at 100 units away from the camera. + + + Cursor velocity properties do not counsider bounds; that is, a cursor will still have an xVelocity + even if it is pressed against the right side of the screen and being moved to the right. + + + + + The change in y since last frame in world units at 100 units away from the camera. + + + Cursor velocity properties do not counsider bounds; that is, a cursor will still have an xVelocity + even if it is pressed against the right side of the screen and being moved to the right. + + + + + The movement rate of the controlling input (usually mouse) on the z axis. + + + Cursor velocity properties do not counsider bounds; that is, a cursor will still have an xVelocity + even if it is pressed against the right side of the screen and being moved to the right. + + + + + The window that the cursor was over when the mouse button was pressed. + + + When the mouse button is pushed down, this value is set - either to null + if the mouse is not over any Windows or to the Window that the mouse is over. + When the mouse button is released (clicked), this value is set to null. + This value is useful for clicking on Windows. Specifically, when the cursor is clicked + on a Button, the WindowPushed value is tested to make sure that it is the same as + the Window clicked on. This allows for players to push on a Button but move + the mouse away and click elsewhere without clicking on the Button originally + pushed and without accidentally clicking on other Buttons. This is also used + with ToggleButtons to control when they are pressed and unpressed. + + + + + Assigns the ObjectGrabbed and calculates the relative position of the + grabbed object. After this method is called, UpdateObjectGrabbedPosition can be called + every frame to position the grabbed object. + + + + + A stack of Windows which demand input from the cursor. + + + When a dominantWindow is valid, the cursor will not be able to interact with other windows. If RemoveInvisibleDominantWindows + is set to true (default is true) then the GuiManager will remove any invisible dominant windows from its + memory. + In other words a DominantWindow can be removed either through the traditional Remove methods or by + setting the Window's Visible property to false if RemoveInvisibleDominantWindows is true. + + + + + Sets the tool tip to show + + + Some UI elements like Buttons automatically show + a tool tip. This property can be used to overwrite + what is shown, or to show tool tips when the user is + over a non-UI element. + + + + + Adds a window as a Dominant Window. If the window is a regular Window + already managed by the GuiManager it will be removed from the regularly-managed + windows. + + The window to add to the Dominant Window stack. + + + + Sorts all contained IWindows according to their + Z values and Layers. This will usually result in + clicks being received in the same order that objects + are drawn, which is what the user will usually expect + + + + + Event raised by the Keyboard every frame on the InputManager's ReceivingInput reference. + + The current IInputReceiver. + + + + Interface for objects which can receive input from the InputManager. + + + + This interface should only be used for Gui elements specifically because the GuiManager will change the InputManager.ReceivingInput + reference depending on the activity of the cursor and other Gui elements. The InputManager will only keep track of one IInputReceiver + at a time, and each IInputReceiver needs to assign itself as the target for input through the static InputManager.ReceivingInput field. + + Since this is only used for Gui elements, this interface is rarely used in games. + + + + + Called by the InputManager automatically every frame. + + + The implementation of this method should raise the FocusUpdate event. + + + + + Called by the engine automatically when an IInputReceiver gains focus. + + + The implementation of this method should raise the GainFocus event. + + + + + Called by the engine automatically when an IInputReceiver loses focus. + + + + + The method called every frame by the InputManager in the Update method + if this is the IInputReceiver referenced by the InputManager. This does + not have to be called automatically. + + + + + A method which determines whether the instance can currently receive focus as an input receiver. + + Whether the instance is taking input. + + + + The next IInputReceiver in the tab sequence. In other words, if this element is currently + receiving input (is the InputManager's ReceivingInput), pressing tab will set the NextInTabSequence + to be the InputManager's ReceivingInput. + + + + + Event raised every frame if there is a non-null InputManager.ReceivingInput. This allows + the IInputReceiver to perform custom every-frame logic when it has focus, such as a ListBox + listening for the Delete key to delete highlighted elements. + + + + + Object which holds a list of strings that can be used + to tell the PropertyGrid which members to exclude when + creating a new PropertyGrid of a specific type. + + + + + Summary description for WindowArray. + + + + + A button or single-axis input device which can return a range of values like the shoulder triggers. + + + + + The current value of the AnalogButton. + + + + + A two-axis input device which can return a range of values on both axes. + + + + + The mDPadOnValue and mDPadOffValue + values are used to simulate D-Pad control + with the analog stick. When the user is above + the absolute value of the mDPadOnValue then it is + as if the DPad is held down. To release the DPad the + value must come under the off value. If there was only + one value then the user could hold the stick near the threshold + and get rapid on/off values due to the inaccuracy of the analog stick. + + + + + Returns the angle of the analog stick. 0 is to the right, Pi/2 is up, Pi is to the left, and 3*Pi/2 is down. + + + + + Gets the distance from the center position of the analog stick. + + + + + The position of the analog stick. The range for each component is -1 to 1. + + + + + Containing functionality for keyboard, mouse, and joystick input. + + + + + Reference to an IInputReceiver which will have its ReceiveInput method called every frame. + + + If this reference is not null, the reference's ReceiveInput method is called in the InputManager.GetInputState method. + + + + + + Returns whether a key was "typed". A type happens either when the user initially pushes a key down, or when + it gets typed again from holding the key down. This works similar to how the keyboard types in text editors + when holding down a key. + + The key to check for. + Whether the key was typed. + + + + The camera-relative X coordinate position of the Mouse at 100 units away. + + + + + Returns whether the Mouse is over the argument Circle. + + The Circle to check. + Whether the mouse is over the argument Circle. + + + + Determines whether the Mouse is over the objectToTest argument. + + + If a Text object is passed this method will only work appropriately if + the Text object has centered text. See the IsOn3D overload which takes a Text argument. + + The type of the first argument. + The object to test if the mouse is on. + Whether the object's Position is relative to the Camera. + + The point where the intersection between the ray casted from the + mouse into the distance and the argument objectToTest occurred. + Whether the mouse is over the argument objectToTest + + + + Grabs a PositionedObject. The PositionedObject will automatically update + its position according to mouse movement while the reference remains. + + + + + Returns the client rectangle-relative X pixel coordinate of the cursor. + + + + + Returns the client rectangle-Y pixel coordinate of the cursor. + + + + + The number of pixels that the mouse has moved on the + X axis during the last frame. + + + + + The number of pixels that the mouse has moved on the + Y axis during the last frame. + + + + + The rate of change of the X property in + pixels per second. + + + + + The rate of change of the Y property in + pixels per second. + + + + + Base interface for class which can record input. + + + + + Information about an input event that has occurred. This class can be used for playbacks + or for saving input information. + + The event type. This can be enum values defined by the end user or enums + provided by other libraries, such as a Keys value representing which key experienced the event. + The type of actions that can be performed on the given EventType. For example + this could be an enum defining whether a key was pressed or released. + + + + Interface for an object which has a specified time. + + + + + The time associated with the object. + + + + + Gets and sets the time that playback started. Setting this value simulates changing + when the playback started. + + + + + Creates a ButtomMap for this controller using the default bindings. This is + a quick way to simulate an Xbox360 controller using the keyboard. + + + This creates the following bindings: + * Left analog stick = arrow keys + * A button = A key + * B button = S key + * X button = Q key + * Y button = W key + * Left trigger = E key + * Right trigger = R key + * Left shoulder = D key + * Right Shoulder = F key + * Back button = Backspace key + * Start button = Enter key + + This will not simulate that the controller is connected, so you will have to set + FakeIsConnected to true if your game checks the connected state. + + + + + Makes this Xbox360Gamepad ignore the argument button for the rest of the current frame. + + The button that should be ignored for the rest of the current frame. + + + + Sets the vibration of the game pad. + + The low-frequency motor. Set between 0.0f and 1.0f + The high-frequency motor. Set between 0.0f and 1.0f + True if the vibration motors were successfully set; false if the controller + was unable to process the request. + + + + + This value can force an Xbox360GamePad's + IsConnected to be true even if the controller + is not connected. This can be used if game logic + requires a certain number of GamePads to be connected. + + + + + Returns the left trigger's current value. When not pressed this property returns + 0.0f. When fully pressed this property returns 1.0f; + + + + + Returns the right trigger's current value. When not pressed this property returns + 0.0f. When fully pressed this property returns 1.0f; + + + + + Enumeration representing the buttons on the Xbox360 controller. The values for each + entry matches the value of the Xbox 360 button index in Managed DirectX. This improves + portability between FlatRedBall Managed DirectX and FlatRedBall XNA. + + + + + Represents an Instruction which can execute an instruction. This is commonly used + with IInstructables to perform logic at a delayed time. + + + + + Class that supports the execution of custom logic at a future time. + + + Instructions are either stored and executed through the InstructionManager or + managed IInstructable instances. + + + + + The system time to execute the instruction at. + + + + + The amount of time to add to the instruction for cycled execution. Default of 0 + instructs the executing logic to not cycle the Instruction. + + + + + Creates and redturns a member-wise clone. + + The clone of the calling instance. + + + + Executes the Instruction. + + + + + Executes an instruction on the target passed as an argument + + + + + The system time to execute the instruction at. + + + The TimeManager.CurrentTime property is used for comparisons. + + + + + The amount of time to add to the instruction for cycled execution. Default of 0 + instructs the executing logic to not cycle the Instruction. + + + + + Gets reference to the object that is the target of the Instruction. + + + + + Base class for typed Instructions. This type can be used + to identify if an Instruction is a generic Instruction. + + + + + Sets the object which this instruction operates on. + + The object that this instruction operates on. + + + + Returns the FullName of the Type that this GenericInstruction operates on. + + + + + The Name of the member (such as "X" or "Width") that this instruction modifies. + + + + + + + + + + The FullName of the type of the Member that is being modified. For example, this would return "System.String" for a float member like X. + + + + + Returns a ToString() representation of the member. + + + + + Returns the value of the member casted as an object. + + + + + Generic method of setting a particular variable at a given time. + + The type of object to operate on (ex. PositionedObject) + The type of the value. For example, the X value in PositionedObject is float. + + + + Used when deserializing .istx files. Not to be called otherwise. + + + + + To be used when inheriting from this class since you won't need the property's name + + The object to operate on (ex. a PositionedObject) + The value to set to the property when the instruction is executed + Absolute time to executing this instruction + + + The object to operate on (ex. a PositionedObject) + The name of the property to set + The value to set to the property when the instruction is executed + Absolute time to executing this instruction + + + + Uses reflection to set the target object's property. + + If you need more performance out of a section, you can simply + inherit from this generic class and override the Execute method to avoid + delegating the call to the late binder class. + + + + List of Instructions which also provides methods for common + actions such as sorting and executing the contained Instructions. + + + + + Executes all contained Instructions in order of index. Contained instructions are not removed. + + + + + Sets the relative rotational velcoities of a PositionedObject so that it rotates + to the desired relative orientation in a given amount of time. + + + This method first sets the relative rotational velocities of the PositionedObject then + sets instructions to stop the relative rotational velocities and + finally sets the rotational values to fix any problems introduced by timing. + + The Sprite to rotate. + The final relative rotX value. + The final relative rotY value. + The final relative rotZ value. + The number of seconds to take in the rotation. + + + + Sets a member on an uncasted object. If the type of objectToSetOn is known, use + LateBinder for performance and safety reasons. + + The object whose field or property should be set. + The name of the field or property to set. + The value of the field or property to set. + + + + Executes contained instructions. + + The number of seconds since the start of application execution. + + + + Holds instructions which will be executed by the InstructionManager + in its Update method (called automatically by FlatRedBallServices). + + + Instructions for managed PositionedObjects like Sprites and Text objects + should be added to the object's internal InstructionList. This prevents instructions + from referencing removed objects and helps with debugging. This list should only be used + on un-managed objects or for instructions which do not associate with a particular object. + + + + + Whether the (automatically called) Update method executes instructions. Default true. + + + + + Represents a set of KeyframeLists which can be applied to objects or + used to perform scripted events. + + + When used to perform scripted events, InstructionSets can be compared + to texture animation. There are many levels of Lists in an InstructionSet. + The following comparison to AnimationChainLists provides some clarity to the + layers: + + InstructionSet (List of KeyframeLists) : AnimationChainList (List of AnimationChains) + + + KeyframeList (List of Keyframes - aka InstructionLists) : AnimationChain (List of AnimationFrames) + + + InstructionList (Applied to an object to change any properties) : AnimationFrame (Applied to an object to change its appearance); + + + + + + Gets a KeyframeList by name. Returns null if none is found + + Name of the KeyframeList to return. + Reference to the KeyframeList with the specified name. + + + + A class that can be used to quickly create create identical Instructions for individual targets. + + Cannot be serialized as an InstructionBlueprint, but can be converted into an InstructionSave via + InstructionSave.FromInstructionBlueprint(). + + + + Builds an Instruction using the information stored in the InstructionBlueprint. + + The object that the returned Instruction will execute on + The current time to use as an offset for the Instruction's Time of execution + If target's type is not this InstructionBlueprint's TargetType + If this InstructionBlueprint was fully initialized before this call to BuildInstruction. + An Instruction created from this InstructionBlueprint's information + + + + A list of InstructionBlueprint objects + + Can be used to quickly create InstructionLists for a specific target. + + + + + Creates an InstructionList containing Instructions that were created by the InstructionBlueprints in + this list. + + The object that the Instructions will be executed on. + The current time to be used as an offset for each Instruction's execution. + + + + A save class containing a list of InstructionSaves. + + + + + Provides interpolation for doubles - used for interpolating between + Keyframes in InstructionSets. + + + + + Generic interface for an interpolator. These are used to interpolate + Keyframes in InstructionSets. + + The type that the Interpolator interpolates. + + + + Base interface for interpolators - to be used in lists. + + + + + Interpolates between the first two arguments using the third as the interpolation value. + + The first value to use in interpolation. + The second value to use in interpolation. + The interpolation value - should be between 0 and 1. + The result of the interpolation. + + + + Interolates between the two argument doubles using the interpolation value. + + The first or starting value. + The end value. + A value between 0 and 1 that determines how + the values are interpolated. + The interpolated value. + + + + An interpolator which interpolates between angle floats. + + + + + An IInterpolator which can interpolate float values. + + + + + An IInterpolator which can interpolate integer values. + + + + + Provides interplation methods for longs. + + + + + Gets a KeyframeList by name. Returns null if none is found + + Name of the KeyframeList to return. + Reference to the KeyframeList with the specified name. + + + + Base abstract class for the generic MethodInstruction class. + + + This class is provided to support lists of MethodInstructions. + + + + + Generic Instruction class which calls a method when executed. + + The type of the object which contains the method to be called. + + + + The game time when the instruction was created. This is compared to the + TimeManager's CurrentTime property when Execute is called to delay the instructions + by the appropriate amount of time. + + + + + Used to pause the timed removal list in the SpriteManager + + + + + Provides a simple interface to late bind a class. + + The first time you attempt to get or set a property, it will dynamically generate the get and/or set + methods and cache them internally. Subsequent gets uses the dynamic methods without having to query the type's + meta data. + + + + Sets the supplied property on the supplied target + + the type of the value + + + + Gets the supplied property on the supplied target + + The type of the property being returned + + + + The instance that this binder operates on by default + + This can be overridden by the caller explicitly passing a target to the indexer + + + + Gets or Sets the supplied property on the contained + + Throws if the contained Instance is null. + + + + Gets or Sets the supplied property on the supplied target + + + + + provides helper functions for late binding a class + + + Class found here: + http://www.codeproject.com/useritems/Dynamic_Code_Generation.asp + + + + + Enumeration used to define relationships between two values. Used in MemberCondition. + + + + + Base class for the generic MemberCondition class provided for List storage. + + + + + Class which can be used to query the relationship of a member relative to a value. This + can be used in scripting and trigger data. + + The type of the object containing the property to compare. + The type of the property to compare. + + + + Stores a list of properties and values for those properties (PropertyValuePair) + + + This class can be used to either store states of objects or abstract the setting of states. + + + + + Base class for the generic TypedMember class. + + + + + Class containing information about a member which can tell if two instances have + identical members. + + The type of the member. + + + + A list of TimedKeyframeLists which represents an Animation which can be + played. + + + This class interfaces with the InstructionSetSaveList class when saving/loading. + + + + + Instruction which calls a Static class' method. + + + This class must be used instead of + FlatRedBall.Instructions.MethodInstruction + because MethodInstruction takes a reference + to an instance of the object containing the method + to call. If the object is a static class then the + compiler will complain about static objects being used + as arguments to methods. + + + + + A list of timed keyframes which can be used to play an animation. + + + + + Stores the related state, velocity, and acceleration values. + + + This is used when interpolating instructions. + + + + + Class responsible for creating ImageDatas from BMP files. + + + + + ImageData takes a fileName (string) and loads the BMP from the file. + + + a new ImageData, containing the width, height, and data of the BMP that was loaded + + + + Class providing methods for interacting with .CSV spreadsheet files. + + + + + Represents a reader that provides fast, non-cached, forward-only access to CSV data. + + + + + Defines the default buffer size. + + + + + Defines the default delimiter character separating each field. + + + + + Defines the default quote character wrapping every field. + + + + + Defines the default escape character letting insert quotation characters inside a quoted field. + + + + + Defines the default comment character indicating that a line is commented out. + + + + + Contains the field header comparer. + + + + + Contains the pointing to the CSV file. + + + + + Contains the buffer size. + + + + + Contains the comment character indicating that a line is commented out. + + + + + Contains the escape character letting insert quotation characters inside a quoted field. + + + + + Contains the delimiter character separating each field. + + + + + Contains the quotation character wrapping every field. + + + + + Indicates if spaces at the start and end of a field are trimmed. + + + + + Indicates if field names are located on the first non commented line. + + + + + Contains the default action to take when a parsing error has occured. + + + + + Contains the action to take when a field is missing. + + + + + Indicates if the reader supports multiline. + + + + + Indicates if the reader will skip empty lines. + + + + + Indicates if the class is initialized. + + + + + Contains the field headers. + + + + + Contains the dictionary of field indexes by header. The key is the field name and the value is its index. + + + + + Contains the current record index in the CSV file. + A value of means that the reader has not been initialized yet. + Otherwise, a negative value means that no record has been read yet. + + + + + Contains the starting position of the next unread field. + + + + + Contains the index of the next unread field. + + + + + Contains the array of the field values for the current record. + A null value indicates that the field have not been parsed. + + + + + Contains the maximum number of fields to retrieve for each record. + + + + + Contains the read buffer. + + + + + Contains the current read buffer length. + + + + + Indicates if the end of the reader has been reached. + + + + + Indicates if the first record is in cache. + This can happen when initializing a reader with no headers + because one record must be read to get the field count automatically + + + + + Indicates if fields are missing for the current record. + Resets after each successful record read. + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + + is a . + + + Cannot read from . + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + The buffer size in bytes. + + is a . + + + Cannot read from . + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + The delimiter character separating each field (default is ','). + + is a . + + + Cannot read from . + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + The delimiter character separating each field (default is ','). + The buffer size in bytes. + + is a . + + + Cannot read from . + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + The delimiter character separating each field (default is ','). + The quotation character wrapping every field (default is '''). + + The escape character letting insert quotation characters inside a quoted field (default is '\'). + If no escape character, set to '\0' to gain some performance. + + The comment character indicating that a line is commented out (default is '#'). + if spaces at the start and end of a field are trimmed, otherwise, . Default is . + + is a . + + + Cannot read from . + + + + + Initializes a new instance of the CsvReader class. + + A pointing to the CSV file. + if field names are located on the first non commented line, otherwise, . + The delimiter character separating each field (default is ','). + The quotation character wrapping every field (default is '''). + + The escape character letting insert quotation characters inside a quoted field (default is '\'). + If no escape character, set to '\0' to gain some performance. + + The comment character indicating that a line is commented out (default is '#'). + if spaces at the start and end of a field are trimmed, otherwise, . Default is . + The buffer size in bytes. + + is a . + + + must be 1 or more. + + + + + Raises the event. + + The that contains the event data. + + + + Gets the field headers. + + The field headers or an empty array if headers are not supported. + + The instance has been disposed of. + + + + + Ensures that the reader is initialized. + + + + + Gets the field index for the provided header. + + The header to look for. + The field index for the provided header. -1 if not found. + + The instance has been disposed of. + + + + + Copies the field array of the current record to a one-dimensional array, starting at the beginning of the target array. + + The one-dimensional that is the destination of the fields of the current record. + + is . + + + + + Copies the field array of the current record to a one-dimensional array, starting at the beginning of the target array. + + The one-dimensional that is the destination of the fields of the current record. + The zero-based index in at which copying begins. + + is . + + + is les than zero or is equal to or greater than the length . + + + No current record. + + + The number of fields in the record is greater than the available space from to the end of . + + + + + Gets the current raw CSV data. + + Used for exception handling purpose. + The current raw CSV data. + + + + Indicates whether the specified Unicode character is categorized as white space. + + A Unicode character. + if is white space; otherwise, . + + + + Moves to the specified record index. + + The record index. + + Record index must be > 0. + + + Cannot move to a previous record in forward-only mode. + + + The instance has been disposed of. + + + + + Parses a new line delimiter. + + The starting position of the parsing. Will contain the resulting end position. + if a new line delimiter was found; otherwise, . + + The instance has been disposed of. + + + + + Determines whether the character at the specified position is a new line delimiter. + + The position of the character to verify. + + if the character at the specified position is a new line delimiter; otherwise, . + + + + + Fills the buffer with data from the reader. + + if data was successfully read; otherwise, . + + The instance has been disposed of. + + + + + Reads the field at the specified index. + Any unread fields with an inferior index will also be read as part of the required parsing. + + The field index. + Indicates if the reader is currently initializing. + Indicates if the value(s) are discarded. + + The field at the specified index. + A indicates that an error occured or that the last field has been reached during initialization. + + + is out of range. + + + There is no current record. + + + The instance has been disposed of. + + + + + Reads the next record. + + if a record has been successfully reads; otherwise, . + + The instance has been disposed of. + + + + + Reads the next record. + + + Indicates if the reader will proceed to the next record after having read headers. + if it stops after having read headers; otherwise, . + + + Indicates if the reader will skip directly to the next line without parsing the current one. + To be used when an error occurs. + + if a record has been successfully reads; otherwise, . + + The instance has been disposed of. + + + + + Skips empty and commented lines. + If the end of the buffer is reached, its content be discarded and filled again from the reader. + + + The position in the buffer where to start parsing. + Will contains the resulting position after the operation. + + if the end of the reader has not been reached; otherwise, . + + The instance has been disposed of. + + + + + Worker method. + Skips empty and commented lines. + + + The position in the buffer where to start parsing. + Will contains the resulting position after the operation. + + + The instance has been disposed of. + + + + + Skips whitespace characters. + + The starting position of the parsing. Will contain the resulting end position. + if the end of the reader has not been reached; otherwise, . + + The instance has been disposed of. + + + + + Skips ahead to the next NewLine character. + If the end of the buffer is reached, its content be discarded and filled again from the reader. + + + The position in the buffer where to start parsing. + Will contains the resulting position after the operation. + + if the end of the reader has not been reached; otherwise, . + + The instance has been disposed of. + + + + + Handles a parsing error. + + The parsing error that occured. + The current position in the buffer. + + is . + + + + + Handles a missing field error. + + The partially parsed value, if available. + The missing field index. + The current position in the raw data. + + The resulting value according to . + If the action is set to , + then the parse error will be handled according to . + + + + + Validates the state of the data reader. + + The validations to accomplish. + + No current record. + + + This operation is invalid when the reader is closed. + + + + + Copy the value of the specified field to an array. + + The index of the field. + The offset in the field value. + The destination array where the field value will be copied. + The destination array offset. + The number of characters to copy from the field value. + + + + + Returns an that can iterate through CSV records. + + An that can iterate through CSV records. + + The instance has been disposed of. + + + + + Returns an that can iterate through CSV records. + + An that can iterate through CSV records. + + The instance has been disposed of. + + + + + Returns an that can iterate through CSV records. + + An that can iterate through CSV records. + + The instance has been disposed of. + + + + + Contains the stack when the object was allocated. + + + + + Contains the disposed status flag. + + + + + Contains the locking object for multi-threading purpose. + + + + + Raises the event. + + A that contains the event data. + + + + Checks if the instance has been disposed of, and if it has, throws an ; otherwise, does nothing. + + + The instance has been disposed of. + + + Derived classes should call this method at the start of all methods and properties that should not be accessed after a call to . + + + + + Releases all resources used by the instance. + + + Calls with the disposing parameter set to to free unmanaged and managed resources. + + + + + Releases the unmanaged resources used by this instance and optionally releases the managed resources. + + + to release both managed and unmanaged resources; to release only unmanaged resources. + + + + + Releases unmanaged resources and performs other cleanup operations before the instance is reclaimed by garbage collection. + + + + + Occurs when there is an error while parsing the CSV stream. + + + + + Gets the comment character indicating that a line is commented out. + + The comment character indicating that a line is commented out. + + + + Gets the escape character letting insert quotation characters inside a quoted field. + + The escape character letting insert quotation characters inside a quoted field. + + + + Gets the delimiter character separating each field. + + The delimiter character separating each field. + + + + Gets the quotation character wrapping every field. + + The quotation character wrapping every field. + + + + Indicates if field names are located on the first non commented line. + + if field names are located on the first non commented line, otherwise, . + + + + Indicates if spaces at the start and end of a field are trimmed. + + if spaces at the start and end of a field are trimmed, otherwise, . + + + + Gets the buffer size. + + + + + Gets or sets the default action to take when a parsing error has occured. + + The default action to take when a parsing error has occured. + + + + Gets or sets the action to take when a field is missing. + + The action to take when a field is missing. + + + + Gets or sets a value indicating if the reader supports multiline fields. + + A value indicating if the reader supports multiline field. + + + + Gets or sets a value indicating if the reader will skip empty lines. + + A value indicating if the reader will skip empty lines. + + + + Gets the maximum number of fields to retrieve for each record. + + The maximum number of fields to retrieve for each record. + + The instance has been disposed of. + + + + + Gets a value that indicates whether the current stream position is at the end of the stream. + + if the current stream position is at the end of the stream; otherwise . + + + + Gets the current record index in the CSV file. + + The current record index in the CSV file. + + + + Gets the field with the specified name and record position. must be . + + + The field with the specified name and record position. + + + is or an empty string. + + + The CSV does not have headers ( property is ). + + + not found. + + + Record index must be > 0. + + + Cannot move to a previous record in forward-only mode. + + + Cannot read record at . + + + The CSV appears to be corrupt at the current position. + + + The instance has been disposed of. + + + + + Gets the field at the specified index and record position. + + + The field at the specified index and record position. + A is returned if the field cannot be found for the record. + + + must be included in [0, [. + + + Record index must be > 0. + + + Cannot move to a previous record in forward-only mode. + + + Cannot read record at . + + + The CSV appears to be corrupt at the current position. + + + The instance has been disposed of. + + + + + Gets the field with the specified name. must be . + + + The field with the specified name. + + + is or an empty string. + + + The CSV does not have headers ( property is ). + + + not found. + + + The CSV appears to be corrupt at the current position. + + + The instance has been disposed of. + + + + + Gets the field at the specified index. + + The field at the specified index. + + must be included in [0, [. + + + No record read yet. Call ReadLine() first. + + + The CSV appears to be corrupt at the current position. + + + The instance has been disposed of. + + + + + Occurs when the instance is disposed of. + + + + + Gets a value indicating whether the instance has been disposed of. + + + if the instance has been disposed of; otherwise, . + + + + + Defines the data reader validations. + + + + + No validation. + + + + + Validate that the data reader is initialized. + + + + + Validate that the data reader is not closed. + + + + + Supports a simple iteration over the records of a . + + + + + Contains the enumerated . + + + + + Contains the current record. + + + + + Contains the current record index. + + + + + Initializes a new instance of the class. + + The to iterate over. + + is a . + + + + + Advances the enumerator to the next record of the CSV. + + if the enumerator was successfully advanced to the next record, if the enumerator has passed the end of the CSV. + + + + Sets the enumerator to its initial position, which is before the first record in the CSV. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + Gets the current record. + + + + + Gets the current record. + + + + + Provides data for the event. + + + + + Contains the error that occured. + + + + + Contains the action to take. + + + + + Initializes a new instance of the ParseErrorEventArgs class. + + The error that occured. + The default action to take. + + + + Gets the error that occured. + + The error that occured. + + + + Gets or sets the action to take. + + The action to take. + + + + Specifies the action to take when a field is missing. + + + + + Treat as a parsing error. + + + + + Replaces by an empty value. + + + + + Replaces by a null value (). + + + + + Specifies the action to take when a parsing error has occured. + + + + + Raises the event. + + + + + Tries to advance to next line. + + + + + Throws an exception. + + + + + A strongly-typed resource class, for looking up localized strings, etc. + + + + + Returns the cached ResourceManager instance used by this class. + + + + + Overrides the current thread's CurrentUICulture property for all + resource lookups using this strongly typed resource class. + + + + + Looks up a localized string similar to Buffer size must be 1 or more.. + + + + + Looks up a localized string similar to Cannot move to a previous record in forward-only mode.. + + + + + Looks up a localized string similar to Cannot read record at index '{0}'.. + + + + + Looks up a localized string similar to Enumeration has either not started or has already finished.. + + + + + Looks up a localized string similar to Collection was modified; enumeration operation may not execute.. + + + + + Looks up a localized string similar to '{0}' field header not found.. + + + + + Looks up a localized string similar to Field index must be included in [0, FieldCount[. Specified field index was : '{0}'.. + + + + + Looks up a localized string similar to The CSV appears to be corrupt near record '{0}' field '{1} at position '{2}'. Current raw data : '{3}'.. + + + + + Looks up a localized string similar to '{0}' is not a supported missing field action.. + + + + + Looks up a localized string similar to No current record.. + + + + + Looks up a localized string similar to The CSV does not have headers (CsvReader.HasHeaders property is false).. + + + + + Looks up a localized string similar to The number of fields in the record is greater than the available space from index to the end of the destination array.. + + + + + Looks up a localized string similar to '{0}' is not a valid ParseErrorAction while inside a ParseError event.. + + + + + Looks up a localized string similar to '{0}' is not a supported ParseErrorAction.. + + + + + Looks up a localized string similar to This operation is invalid when the reader is closed.. + + + + + Looks up a localized string similar to Record index must be 0 or more.. + + + + + The name of the header - such as "Health" + + + + + Whether objects must have a value in this header. This is true for dictionaries, and to + help the csv deserialization understand when a column belongs to a new instance or if it is + a column used for lists. + + + + + Represents the raw data loaded from a csv file. This is + used if the data must be processed or converted by hand to + other object types. + + + + + Returns whether the file exists considering the relative directory. + + The file to search for. + Whether the argument file exists. + + + + Searches the passed directory and all subdirectories for the passed file. + + The name of the file including extension. + The directory to search in, including all subdirectories. + The full path of the first file found matching the name, or an empty string if none is found. + + + + Searches the executable's director and all subdirectories for the passed file. + + The name of the file which may or may not include an extension. + The full path of the first file found matching the name, or an empty string if none is found + + + + Returns the extension in a filename. + + + The extension returned will not contain a period. + + + + // this code will return a string containing "png", not ".png" + FileManager.GetExtension(@"FolderName/myImage.png"); + + + + The filename. + Returns the extension or an empty string if no period is found in the filename. + + + + Returns a List containing all of the files found in a particular directory and its subdirectories. + + The directory to search in. + + + + + Returns a List containing all files which match the fileType argument which are + in the directory argument or a subfolder. This recurs, returning all files. + + String representing the directory to search. If an empty + string is passed, the method will search starting in the directory holding the .exe. + The file type to search for specified as an extension. The extension + can either have a period or not. That is ".jpg" and "jpg" are both valid fileType arguments. An empty + or null value for this parameter will return all files regardless of file type. + A list containing all of the files found which match the fileType. + + + + Returns a List containing all files which match the fileType argument which are within + the depthToSearch folder range relative to the directory argument. + + String representing the directory to search. If an empty + string is passed, the method will search starting in the directory holding the .exe. + The file type to search for specified as an extension. The extension + can either have a period or not. That is ".jpg" and "jpg" are both valid fileType arguments. An empty + or null value for this parameter will return all files regardless of file type. + The depth to search through. If the depthToSearch + is 0, only the argument directory will be searched. + A list containing all of the files found which match the fileType. + + + + Determines whether a particular file is a graphical file that can be loaded by the FRB Engine. + + + This method does conducts the simple test of looking at the extension of the filename. If the extension inaccurately + represents the actual format of the file, the method may also inaccurately report whether the file is graphical. + + The file name to test. + Whether the file is a graphic file. + + + + Returns the fileName without an extension, or makes no changes if fileName has no extension. + + The file name. + The file name with extension removed if an extension existed. + + + + Modifies the fileName by removing its path, or makes no changes if the fileName has no path. + + The file name to change + + + + Returns the fileName without a path, or makes no changes if the fileName has no path. + + The file name. + The modified fileName if a path is found. + + + + Sets the relative directory to the current directory. + + + The current directory is not necessarily the same as the directory of the .exe. If the + .exe is called from a different location (such as the command line in a different folder), + the current directory will differ. + + + + + Replaces back slashes with forward slashes, but + doesn't break network addresses. + + The string to replace slashes in. + + + + The directory that FlatRedBall will use when loading assets. Defaults to the application's directory. + + + + + Gets the path to the user specific application data directory. + + If your game/application will be writing anything to the file system, you will want + to do so somewhere in this directory. The reason for this is because you cannot anticipate + whether the user will have the needed permissions to write to the directory where the + executable lives. + C:\Documents and Settings\<username>\Application Data + + + + Contains an indexed array of Colors to be used by images with ColorType 3 and possibly ColorTypes + 2 and 6. + + + + + Simple struct used to hold sample values. + + + + + Class responsible for loading GIF files. + + + For information, see: + http://www.fileformat.info/format/gif/ + + + + + A list of ImageDatas with timing information on each element. + + + This class is used when loading GIF files to an AnimationChain. + + + + This class let's you easily make HTTP requests and, if required, + deserialize the results into local strongly typed objects. Callbacks called + on separate thread. + + + Simple HTTP Get, gives you the string result + + + Simple HTTP Get, gives you the string result + + + Does an HTTP Get, and deserializes the XML result (assumes UTF8) with the File Manager Deserializer. + + + Does an HTTP Get, and deserializes the XML result (assumes UTF8) with the File Manager Deserializer. + + + Does an HTTP Get, and deserializes the JSON result (assumes UTF8) with the . + + + Does an HTTP Get, and deserializes the JSON result (assumes UTF8) with the . + + + + Computes Adler32 checksum for a stream of data. An Adler32 + checksum is not as reliable as a CRC32 checksum, but a lot faster to + compute. + + The specification for Adler32 may be found in RFC 1950. + ZLIB Compressed Data Format Specification version 3.3) + + + From that document: + + "ADLER32 (Adler-32 checksum) + This contains a checksum value of the uncompressed data + (excluding any dictionary data) computed according to Adler-32 + algorithm. This algorithm is a 32-bit extension and improvement + of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + standard. + + Adler-32 is composed of two sums accumulated per byte: s1 is + the sum of all bytes, s2 is the sum of all s1 values. Both sums + are done modulo 65521. s1 is initialized to 1, s2 to zero. The + Adler-32 checksum is stored as s2*65536 + s1 in most- + significant-byte first (network) order." + + "8.2. The Adler-32 algorithm + + The Adler-32 algorithm is much faster than the CRC32 algorithm yet + still provides an extremely low probability of undetected errors. + + The modulo on unsigned long accumulators can be delayed for 5552 + bytes, so the modulo operation time is negligible. If the bytes + are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + and order sensitive, unlike the first sum, which is just a + checksum. That 65521 is prime is important to avoid a possible + large class of two-byte errors that leave the check unchanged. + (The Fletcher checksum uses 255, which is not prime and which also + makes the Fletcher check insensitive to single byte changes 0 - + 255.) + + The sum s1 is initialized to 1 instead of zero to make the length + of the sequence part of s2, so that the length does not have to be + checked separately. (Any sequence of zeroes has a Fletcher + checksum of zero.)" + + + + + + + Interface to compute a data checksum used by checked input/output streams. + A data checksum can be updated by one byte or with a byte array. After each + update the value of the current checksum can be returned by calling + getValue. The complete checksum object can also be reset + so it can be used again with new data. + + + + + Resets the data checksum as if no update was ever called. + + + + + Adds one byte to the data checksum. + + + the data value to add. The high byte of the int is ignored. + + + + + Updates the data checksum with the bytes taken from the array. + + + buffer an array of bytes + + + + + Adds the byte array to the data checksum. + + + The buffer which contains the data + + + The offset in the buffer where the data starts + + + the number of data bytes to add. + + + + + Returns the data checksum computed so far. + + + + + largest prime smaller than 65536 + + + + + Creates a new instance of the Adler32 class. + The checksum starts off with a value of 1. + + + + + Resets the Adler32 checksum to the initial value. + + + + + Updates the checksum with a byte value. + + + The data value to add. The high byte of the int is ignored. + + + + + Updates the checksum with an array of bytes. + + + The source of the data to update with. + + + + + Updates the checksum with the bytes taken from the array. + + + an array of bytes + + + the start of the data used for this update + + + the number of bytes to use for this update + + + + + Returns the Adler32 data checksum computed so far. + + + + + SharpZipBaseException is the base exception class for the SharpZipLibrary. + All library exceptions are derived from this. + + NOTE: Not all exceptions thrown will be derived from this class. + A variety of other exceptions are possible for example + + + + Initializes a new instance of the SharpZipBaseException class. + + + + + Initializes a new instance of the SharpZipBaseException class with a specified error message. + + A message describing the exception. + + + + Initializes a new instance of the SharpZipBaseException class with a specified + error message and a reference to the inner exception that is the cause of this exception. + + A message describing the exception. + The inner exception + + + + This is the Deflater class. The deflater class compresses input + with the deflate algorithm described in RFC 1951. It has several + compression levels and three different strategies described below. + + This class is not thread safe. This is inherent in the API, due + to the split of deflate and setInput. + + author of the original java version : Jochen Hoenicke + + + + + The best and slowest compression level. This tries to find very + long and distant string repetitions. + + + + + The worst but fastest compression level. + + + + + The default compression level. + + + + + This level won't compress at all but output uncompressed blocks. + + + + + The compression method. This is the only method supported so far. + There is no need to use this constant at all. + + + + + Creates a new deflater with default compression level. + + + + + Creates a new deflater with given compression level. + + + the compression level, a value between NO_COMPRESSION + and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + + if lvl is out of range. + + + + Creates a new deflater with given compression level. + + + the compression level, a value between NO_COMPRESSION + and BEST_COMPRESSION. + + + true, if we should suppress the Zlib/RFC1950 header at the + beginning and the adler checksum at the end of the output. This is + useful for the GZIP/PKZIP formats. + + if lvl is out of range. + + + + Resets the deflater. The deflater acts afterwards as if it was + just created with the same compression level and strategy as it + had before. + + + + + Flushes the current input block. Further calls to deflate() will + produce enough output to inflate everything in the current input + block. This is not part of Sun's JDK so I have made it package + private. It is used by DeflaterOutputStream to implement + flush(). + + + + + Finishes the deflater with the current input block. It is an error + to give more input after this method was called. This method must + be called to force all bytes to be flushed. + + + + + Sets the data which should be compressed next. This should be only + called when needsInput indicates that more input is needed. + If you call setInput when needsInput() returns false, the + previous input that is still pending will be thrown away. + The given byte array should not be changed, before needsInput() returns + true again. + This call is equivalent to setInput(input, 0, input.length). + + + the buffer containing the input data. + + + if the buffer was finished() or ended(). + + + + + Sets the data which should be compressed next. This should be + only called when needsInput indicates that more input is needed. + The given byte array should not be changed, before needsInput() returns + true again. + + + the buffer containing the input data. + + + the start of the data. + + + the number of data bytes of input. + + + if the buffer was Finish()ed or if previous input is still pending. + + + + + Sets the compression level. There is no guarantee of the exact + position of the change, but if you call this when needsInput is + true the change of compression level will occur somewhere near + before the end of the so far given input. + + + the new compression level. + + + + + Get current compression level + + Returns the current compression level + + + + Sets the compression strategy. Strategy is one of + DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + position where the strategy is changed, the same as for + SetLevel() applies. + + + The new compression strategy. + + + + + Deflates the current input block with to the given array. + + + The buffer where compressed data is stored + + + The number of compressed bytes added to the output, or 0 if either + IsNeedingInput() or IsFinished returns true or length is zero. + + + + + Deflates the current input block to the given array. + + + Buffer to store the compressed data. + + + Offset into the output array. + + + The maximum number of bytes that may be stored. + + + The number of compressed bytes added to the output, or 0 if either + needsInput() or finished() returns true or length is zero. + + + If Finish() was previously called. + + + If offset or length don't match the array length. + + + + + Sets the dictionary which should be used in the deflate process. + This call is equivalent to setDictionary(dict, 0, dict.Length). + + + the dictionary. + + + if SetInput () or Deflate () were already called or another dictionary was already set. + + + + + Sets the dictionary which should be used in the deflate process. + The dictionary is a byte array containing strings that are + likely to occur in the data which should be compressed. The + dictionary is not stored in the compressed output, only a + checksum. To decompress the output you need to supply the same + dictionary again. + + + The dictionary data + + + The index where dictionary information commences. + + + The number of bytes in the dictionary. + + + If SetInput () or Deflate() were already called or another dictionary was already set. + + + + + Compression level. + + + + + If true no Zlib/RFC1950 headers or footers are generated + + + + + The current state. + + + + + The total bytes of output written. + + + + + The pending output. + + + + + The deflater engine. + + + + + Gets the current adler checksum of the data that was processed so far. + + + + + Gets the number of input bytes processed so far. + + + + + Gets the number of output bytes so far. + + + + + Returns true if the stream was finished and no more output bytes + are available. + + + + + Returns true, if the input buffer is empty. + You should then call setInput(). + NOTE: This method can also return true when the stream + was finished. + + + + + This class contains constants used for deflation. + + + + + Set to true to enable debugging + + + + + Written to Zip file to identify a stored block + + + + + Identifies static tree in Zip file + + + + + Identifies dynamic tree in Zip file + + + + + Header flag indicating a preset dictionary for deflation + + + + + Sets internal buffer sizes for Huffman encoding + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Internal compression engine constant + + + + + Strategies for deflater + + + + + The default strategy + + + + + This strategy will only allow longer string repetitions. It is + useful for random data with a small character set. + + + + + This strategy will not look for string repetitions at all. It + only encodes with Huffman trees (which means, that more common + characters get a smaller encoding. + + + + + Low level compression engine for deflate algorithm which uses a 32K sliding window + with secondary compression from Huffman/Shannon-Fano codes. + + + + + Construct instance with pending buffer + + + Pending buffer to use + > + + + + Deflate drives actual compression of data + + True to flush input buffers + Finish deflation with the current input. + Returns true if progress has been made. + + + + Sets input data to be deflated. Should only be called when NeedsInput() + returns true + + The buffer containing input data. + The offset of the first byte of data. + The number of bytes of data to use as input. + + + + Determines if more input is needed. + + Return true if input is needed via SetInput + + + + Set compression dictionary + + The buffer containing the dictionary data + The offset in the buffer for the first byte of data + The length of the dictionary data. + + + + Reset internal state + + + + + Reset Adler checksum + + + + + Set the deflate level (0-9) + + The value to set the level to. + + + + Fill the window + + + + + Inserts the current string in the head hash and returns the previous + value for this hash. + + The previous hash value + + + + Find the best (longest) string in the window matching the + string starting at strstart. + + Preconditions: + + strstart + MAX_MATCH <= window.length. + + + True if a match greater than the minimum length is found + + + + Hashtable, hashing three characters to an index for window, so + that window[index]..window[index+2] have this hash code. + Note that the array should really be unsigned short, so you need + to and the values with 0xffff. + + + + + prev[index & WMASK] points to the previous index that has the + same hash code as the string starting at index. This way + entries with the same hash code are in a linked list. + Note that the array should really be unsigned short, so you need + to and the values with 0xffff. + + + + + Points to the current character in the window. + + + + + lookahead is the number of characters starting at strstart in + window that are valid. + So window[strstart] until window[strstart+lookahead-1] are valid + characters. + + + + + This array contains the part of the uncompressed stream that + is of relevance. The current character is indexed by strstart. + + + + + The current compression function. + + + + + The input data for compression. + + + + + The total bytes of input read. + + + + + The offset into inputBuf, where input data starts. + + + + + The end offset of the input data. + + + + + The adler checksum + + + + + Get current value of Adler checksum + + + + + Total data processed + + + + + Get/set the deflate strategy + + + + + This is the DeflaterHuffman class. + + This class is not thread safe. This is inherent in the API, due + to the split of Deflate and SetInput. + + author of the original java version : Jochen Hoenicke + + + + + Pending buffer to use + + + + + Construct instance with pending buffer + + Pending buffer to use + + + + Reset internal state + + + + + Write all trees to pending buffer + + The number/rank of treecodes to send. + + + + Compress current buffer writing data to pending buffer + + + + + Flush block to output with no compression + + Data to write + Index of first byte to write + Count of bytes to write + True if this is the last block + + + + Flush block to output with compression + + Data to flush + Index of first byte to flush + Count of bytes to flush + True if this is the last block + + + + Get value indicating if internal buffer is full + + true if buffer is full + + + + Add literal to buffer + + Literal value to add to buffer. + Value indicating internal buffer is full + + + + Add distance code and length to literal and distance trees + + Distance code + Length + Value indicating if internal buffer is full + + + + Reverse the bits of a 16 bit value. + + Value to reverse bits + Value with bits reversed + + + + Resets the internal state of the tree + + + + + Check that all frequencies are zero + + + At least one frequency is non-zero + + + + + Set static codes and length + + new codes + length for new codes + + + + Build dynamic codes and lengths + + + + + Get encoded length + + Encoded length, the sum of frequencies * lengths + + + + Scan a literal or distance tree to determine the frequencies of the codes + in the bit length tree. + + + + + Write tree values + + Tree to write + + + + This class stores the pending output of the Deflater. + + author of the original java version : Jochen Hoenicke + + + + + This class is general purpose class for writing data to a buffer. + + It allows you to write bits as well as bytes + Based on DeflaterPending.java + + author of the original java version : Jochen Hoenicke + + + + + Internal work buffer + + + + + construct instance using default buffer size of 4096 + + + + + construct instance using specified buffer size + + + size to use for internal buffer + + + + + Clear internal state/buffers + + + + + Write a byte to buffer + + + The value to write + + + + + Write a short value to buffer LSB first + + + The value to write. + + + + + write an integer LSB first + + The value to write. + + + + Write a block of data to buffer + + data to write + offset of first byte to write + number of bytes to write + + + + Align internal buffer on a byte boundary + + + + + Write bits to internal buffer + + source of bits + number of bits to write + + + + Write a short value to internal buffer most significant byte first + + value to write + + + + Flushes the pending buffer into the given output array. If the + output array is to small, only a partial flush is done. + + The output array. + The offset into output array. + The maximum number of bytes to store. + The number of bytes flushed. + + + + Convert internal buffer to byte array. + Buffer is empty on completion + + + The internal buffer contents converted to a byte array. + + + + + The number of bits written to the buffer + + + + + Indicates if buffer has been flushed + + + + + Construct instance with default buffer size + + + + + Inflater is used to decompress data that has been compressed according + to the "deflate" standard described in rfc1951. + + By default Zlib (rfc1950) headers and footers are expected in the input. + You can use constructor public Inflater(bool noHeader) passing true + if there is no Zlib header information + + The usage is as following. First you have to set some input with + SetInput(), then Inflate() it. If inflate doesn't + inflate any bytes there may be three reasons: +
    +
  • IsNeedingInput() returns true because the input buffer is empty. + You have to provide more input with SetInput(). + NOTE: IsNeedingInput() also returns true when, the stream is finished. +
  • +
  • IsNeedingDictionary() returns true, you have to provide a preset + dictionary with SetDictionary().
  • +
  • IsFinished returns true, the inflater has finished.
  • +
+ Once the first output byte is produced, a dictionary will not be + needed at a later stage. + + author of the original java version : John Leuner, Jochen Hoenicke +
+
+ + + These are the possible states for an inflater + + + + + Copy lengths for literal codes 257..285 + + + + + Extra bits for literal codes 257..285 + + + + + Copy offsets for distance codes 0..29 + + + + + Extra bits for distance codes + + + + + This variable contains the current state. + + + + + The adler checksum of the dictionary or of the decompressed + stream, as it is written in the header resp. footer of the + compressed stream. + Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + + + + + The number of bits needed to complete the current state. This + is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + + + + + True, if the last block flag was set in the last block of the + inflated stream. This means that the stream ends after the + current block. + + + + + The total number of inflated bytes. + + + + + The total number of bytes set with setInput(). This is not the + value returned by the TotalIn property, since this also includes the + unprocessed input. + + + + + This variable stores the noHeader flag that was given to the constructor. + True means, that the inflated stream doesn't contain a Zlib header or + footer. + + + + + Creates a new inflater or RFC1951 decompressor + RFC1950/Zlib headers and footers will be expected in the input data + + + + + Creates a new inflater. + + + True if no RFC1950/Zlib header and footer fields are expected in the input data + + This is used for GZIPed/Zipped input. + + For compatibility with + Sun JDK you should provide one byte of input more than needed in + this case. + + + + + Resets the inflater so that a new stream can be decompressed. All + pending input and output will be discarded. + + + + + Decodes a zlib/RFC1950 header. + + + False if more input is needed. + + + The header is invalid. + + + + + Decodes the dictionary checksum after the deflate header. + + + False if more input is needed. + + + + + Decodes the huffman encoded symbols in the input stream. + + + false if more input is needed, true if output window is + full or the current block ends. + + + if deflated stream is invalid. + + + + + Decodes the adler checksum after the deflate stream. + + + false if more input is needed. + + + If checksum doesn't match. + + + + + Decodes the deflated stream. + + + false if more input is needed, or if finished. + + + if deflated stream is invalid. + + + + + Sets the preset dictionary. This should only be called, if + needsDictionary() returns true and it should set the same + dictionary, that was used for deflating. The getAdler() + function returns the checksum of the dictionary needed. + + + The dictionary. + + + + + Sets the preset dictionary. This should only be called, if + needsDictionary() returns true and it should set the same + dictionary, that was used for deflating. The getAdler() + function returns the checksum of the dictionary needed. + + + The dictionary. + + + The index into buffer where the dictionary starts. + + + The number of bytes in the dictionary. + + + No dictionary is needed. + + + The adler checksum for the buffer is invalid + + + + + Sets the input. This should only be called, if needsInput() + returns true. + + + the input. + + + + + Sets the input. This should only be called, if needsInput() + returns true. + + + The source of input data + + + The index into buffer where the input starts. + + + The number of bytes of input to use. + + + No input is needed. + + + The index and/or count are wrong. + + + + + Inflates the compressed stream to the output buffer. If this + returns 0, you should check, whether IsNeedingDictionary(), + IsNeedingInput() or IsFinished() returns true, to determine why no + further output is produced. + + + the output buffer. + + + The number of bytes written to the buffer, 0 if no further + output can be produced. + + + if buffer has length 0. + + + if deflated stream is invalid. + + + + + Inflates the compressed stream to the output buffer. If this + returns 0, you should check, whether needsDictionary(), + needsInput() or finished() returns true, to determine why no + further output is produced. + + + the output buffer. + + + the offset in buffer where storing starts. + + + the maximum number of bytes to output. + + + the number of bytes written to the buffer, 0 if no further output can be produced. + + + if count is less than 0. + + + if the index and / or count are wrong. + + + if deflated stream is invalid. + + + + + Returns true, if the input buffer is empty. + You should then call setInput(). + NOTE: This method also returns true when the stream is finished. + + + + + Returns true, if a preset dictionary is needed to inflate the input. + + + + + Returns true, if the inflater has finished. This means, that no + input is needed and no output can be produced. + + + + + Gets the adler checksum. This is either the checksum of all + uncompressed bytes returned by inflate(), or if needsDictionary() + returns true (and thus no output was yet produced) this is the + adler checksum of the expected dictionary. + + + the adler checksum. + + + + + Gets the total number of output bytes returned by Inflate(). + + + the total number of output bytes. + + + + + Gets the total number of processed compressed input bytes. + + + The total number of bytes of processed input bytes. + + + + + Gets the number of unprocessed input bytes. Useful, if the end of the + stream is reached and you want to further process the bytes after + the deflate stream. + + + The number of bytes of the input which have not been processed. + + + + + Huffman tree used for inflation + + + + + Literal length tree + + + + + Distance tree + + + + + Constructs a Huffman tree from the array of code lengths. + + + the array of code lengths + + + + + Reads the next symbol from input. The symbol is encoded using the + huffman tree. + + + input the input source. + + + the next symbol, or -1 if not enough input is available. + + + + + An input buffer customised for use by + + + The buffer supports decryption of incoming data. + + + + + Initialise a new instance of with a default buffer size + + The stream to buffer. + + + + Initialise a new instance of + + The stream to buffer. + The size to use for the buffer + A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB. + + + + Call passing the current clear text buffer contents. + + The inflater to set input for. + + + + Fill the buffer from the underlying input stream. + + + + + Read a buffer directly from the input stream + + The buffer to fill + Returns the number of bytes read. + + + + Read a buffer directly from the input stream + + The buffer to read into + The offset to start reading data into. + The number of bytes to read. + Returns the number of bytes read. + + + + Read clear text data from the input stream. + + The buffer to add data to. + The offset to start adding data at. + The number of bytes to read. + Returns the number of bytes actually read. + + + + Read a from the input stream. + + Returns the byte read. + + + + Read an in little endian byte order. + + The short value read case to an int. + + + + Read an in little endian byte order. + + The int value read. + + + + Read a in little endian byte order. + + The long value read. + + + + Get the length of bytes bytes in the + + + + + Get the contents of the raw data buffer. + + This may contain encrypted data. + + + + Get the number of useable bytes in + + + + + Get the contents of the clear text buffer. + + + + + Get/set the number of bytes available + + + + + This filter stream is used to decompress data compressed using the "deflate" + format. The "deflate" format is described in RFC 1951. + + This stream may form the basis for other decompression filters, such + as the GZipInputStream. + + Author of the original java version : John Leuner. + + + + + Create an InflaterInputStream with the default decompressor + and a default buffer size of 4KB. + + + The InputStream to read bytes from + + + + + Create an InflaterInputStream with the specified decompressor + and a default buffer size of 4KB. + + + The source of input data + + + The decompressor used to decompress data read from baseInputStream + + + + + Create an InflaterInputStream with the specified decompressor + and the specified buffer size. + + + The InputStream to read bytes from + + + The decompressor to use + + + Size of the buffer to use + + + + + Skip specified number of bytes of uncompressed data + + + Number of bytes to skip + + + The number of bytes skipped, zero if the end of + stream has been reached + + + The number of bytes to skip is less than or equal to zero. + + + + + Clear any cryptographic state. + + + + + Fills the buffer with more data to decompress. + + + Stream ends early + + + + + Flushes the baseInputStream + + + + + Sets the position within the current stream + Always throws a NotSupportedException + + The relative offset to seek to. + The defining where to seek from. + The new position in the stream. + Any access + + + + Set the length of the current stream + Always throws a NotSupportedException + + The new length value for the stream. + Any access + + + + Writes a sequence of bytes to stream and advances the current position + This method always throws a NotSupportedException + + Thew buffer containing data to write. + The offset of the first byte to write. + The number of bytes to write. + Any access + + + + Writes one byte to the current stream and advances the current position + Always throws a NotSupportedException + + The byte to write. + Any access + + + + Entry point to begin an asynchronous write. Always throws a NotSupportedException. + + The buffer to write data from + Offset of first byte to write + The maximum number of bytes to write + The method to be called when the asynchronous write operation is completed + A user-provided object that distinguishes this particular asynchronous write request from other requests + An IAsyncResult that references the asynchronous write + Any access + + + + Closes the input stream. When + is true the underlying stream is also closed. + + + + + Reads decompressed data into the provided buffer byte array + + + The array to read and decompress data into + + + The offset indicating where the data should be placed + + + The number of bytes to decompress + + The number of bytes read. Zero signals the end of stream + + Inflater needs a dictionary + + + + + Decompressor for this stream + + + + + Input buffer for this stream. + + + + + Base stream the inflater reads from. + + + + + The compressed size + + + + + Flag indicating wether this instance has been closed or not. + + + + + Flag indicating wether this instance is designated the stream owner. + When closing if this flag is true the underlying stream is closed. + + + + + Get/set flag indicating ownership of underlying stream. + When the flag is true will close the underlying stream also. + + + The default value is true. + + + + + Returns 0 once the end of the stream (EOF) has been reached. + Otherwise returns 1. + + + + + Gets a value indicating whether the current stream supports reading + + + + + Gets a value of false indicating seeking is not supported for this stream. + + + + + Gets a value of false indicating that this stream is not writeable. + + + + + A value representing the length of the stream in bytes. + + + + + The current position within the stream. + Throws a NotSupportedException when attempting to set the position + + Attempting to set the position + + + + Contains the output from the Inflation process. + We need to have a window so that we can refer backwards into the output stream + to repeat stuff.
+ Author of the original java version : John Leuner +
+
+ + + Write a byte to this output window + + value to write + + if window is full + + + + + Append a byte pattern already in the window itself + + length of pattern to copy + distance from end of window pattern occurs + + If the repeated data overflows the window + + + + + Copy from input manipulator to internal window + + source of data + length of data to copy + the number of bytes copied + + + + Copy dictionary to window + + source dictionary + offset of start in source dictionary + length of dictionary + + If window isnt empty + + + + + Get remaining unfilled space in window + + Number of bytes left in window + + + + Get bytes available for output in window + + Number of bytes filled + + + + Copy contents of window to output + + buffer to copy to + offset to start at + number of bytes to count + The number of bytes copied + + If a window underflow occurs + + + + + Reset by clearing window so GetAvailable returns 0 + + + + + This class allows us to retrieve a specified number of bits from + the input buffer, as well as copy big byte blocks. + + It uses an int buffer to store up to 31 bits for direct + manipulation. This guarantees that we can get at least 16 bits, + but we only need at most 15, so this is all safe. + + There are some optimizations in this class, for example, you must + never peek more than 8 bits more than needed, and you must first + peek bits before you may drop them. This is not a general purpose + class but optimized for the behaviour of the Inflater. + + authors of the original java version : John Leuner, Jochen Hoenicke + + + + + Constructs a default StreamManipulator with all buffers empty + + + + + Get the next sequence of bits but don't increase input pointer. bitCount must be + less or equal 16 and if this call succeeds, you must drop + at least n - 8 bits in the next call. + + The number of bits to peek. + + the value of the bits, or -1 if not enough bits available. */ + + + + + Drops the next n bits from the input. You should have called PeekBits + with a bigger or equal n before, to make sure that enough bits are in + the bit buffer. + + The number of bits to drop. + + + + Gets the next n bits and increases input pointer. This is equivalent + to followed by , except for correct error handling. + + The number of bits to retrieve. + + the value of the bits, or -1 if not enough bits available. + + + + + Skips to the next byte boundary. + + + + + Copies bytes from input buffer to output buffer starting + at output[offset]. You have to make sure, that the buffer is + byte aligned. If not enough bytes are available, copies fewer + bytes. + + + The buffer to copy bytes to. + + + The offset in the buffer at which copying starts + + + The length to copy, 0 is allowed. + + + The number of bytes copied, 0 if no bytes were available. + + + Length is less than zero + + + Bit buffer isnt byte aligned + + + + + Resets state and empties internal buffers + + + + + Add more input for consumption. + Only call when IsNeedingInput returns true + + data to be input + offset of first byte of input + number of bytes of input to add. + + + + Gets the number of bits available in the bit buffer. This must be + only called when a previous PeekBits() returned -1. + + + the number of bits available. + + + + + Gets the number of bytes available. + + + The number of bytes available. + + + + + Returns true when SetInput can be called + + + + + Represents exception conditions specific to Zip archive handling + + + + + Initializes a new instance of the ZipException class. + + + + + Initializes a new instance of the ZipException class with a specified error message. + + The error message that explains the reason for the exception. + + + + Initialise a new instance of ZipException. + + A message describing the error. + The exception that is the cause of the current exception. + + + + Notes: Interlaced pngs are now fully supported + Only the chunks of data that are outlined in the Png specification as Critical Chunks are supported. + + TODO: Allow use of the CRC values to check the validity of the Chunk data + Consider implementing the Ancillary Chunks since chunks like gAMA can drastically change visible colors. + + + + + Arranges and combines the byte values stored in byte[] bytes in order to form + a uint. + + A byte[] of length 4 to be formed into a uint. + The uint represented by byte[] bytes. + + + + Uses the ColorType and Bitdepth of this image to calculate the number of bytes per pixel. + + The HeaderInfo containing IHDR data for this image + The number of BytesPerPixel in this image. + + + + Reads the data in the given Stream and stores it in a byte[] that is then trimmed and returned. + + A read-enabled Stream that is reading from a .png file. + A byte[] containing the file being read through Stream stream. + + + + Loads a file (hopefully a .png image) into a byte[]. + + The name of the .png file to be loaded + A Byte[] containing the file referred to by fileName. + + + + Reverses the filtering done on a scanline of pixels that have been filtered using Method 0 (Subtract) + + A reference to the byte[] containing the image(decompressed) + The index of the byte where this scanline begins(after the filter byte) + The number of bits per scanline. + The number of bytes per pixel + The number of bits per sample + + + + Reverses the filtering done on a scanline of pixels that have been filtered using Method 1 (Up) + + A reference to the byte[] containing the image(decompressed) + The index of the byte where this scanline begins(after the filter byte) + The number of bits per scanline. + The number of bytes per pixel + The number of bits per sample + + + + Reverses the filtering done on a scanline of pixels that have been filtered using Method 2 (Average) + + A reference to the byte[] containing the image(decompressed) + The index of the byte where this scanline begins(after the filter byte) + The number of bits per scanline. + The number of bytes per pixel + The number of bits per sample + + + + Reverses the filtering done on a scanline of pixels that have been filtered using Method 3 (Paeth) + + A reference to the byte[] containing the image(decompressed) + The index of the byte where this scanline begins(after the filter byte) + The number of bits per scanline. + The number of bytes per pixel + The number of bits per sample + + + + A helper for the ReverseFilterPaeth method + + + + + + + + + Calls the appropriate Reverse Filtering methods to return the image to its original state. + + The byte[] containing the filtered(but decompressed) image. + A HeaderInfo struct containing IHDR data for this image. + A byte[] containing the restored, original image(in bytes) + + + + Reads the bytes of a chunk's ChunkData segment from the Stream and returns it in a + byte[]. + + A Stream pointing to the .png image's data. + The given length of the ChunkData (Should be the value provided by the Chunk). + A byte[] containing ChunkData from the Stream + + + + Converts a 4-byte uint into an enumerated ChunkType + + The 4-byte value read from the .png image to signify a Chunk type + The enumerated ChunkType matching the uint passed in + + + + Reads the length, type, data and CRC of the next chunk in the file, and then forms a Chunk + struct using this data. + + A Stream pointing to the byte data of the .png image. + A reference to the List of chunks that this Chunk will be stored in. + True if the Chunk read isn't the IEND chunk AND the end of the file hasn't been reached. + + + + Processes the IHDR chunk by reading and storing all of its information in a HeaderInfo struct. + + A List containing all of the Chunks for this .png image + A HeaderInfo struct containing all of the information of this images IHDR chunk + + + + Processes the PLTE chunk by reading in all of the indexed color values and storing them in + a PaletteInfo struct. + + The Chunk containing the PLTE chunk from the file + An unused PaletteInfo struct to be filled by data from plte + + + + Takes a List of all of the Chunks in the .png image being loaded (after IHDR is loaded and removed) + and processes each Chunk appropriately before returning the compressed image. NOTE: Currently only + processes the required Chunks PLTE, IDAT and IEND. Ancillary chunks are currently unsupported. + + The List of chunks loaded from the .png image file + The HeaderInfo loaded from the IHDR chunk + An unused PaletteInfo struct to load the PLTE chunk data into + A byte[] containing the constructed and decompressed image + + + + Checks the first 8 bytes of the file in the given Stream against the standard signature + for .png images. + + The byte Stream pointing to data from the .png file + True if the Stream file contains a valid .png image + + + + Reads data from the given IDAT chunk and appends it to the List imageData. + + A Chunk containing a compressed section of the image. + A List of bytes to hold the entire compressed image. + + + + Reads the decompressed(inflated) image data from an InflaterInputStream that was fed the compressed + image. + + The byte[] to store the decompressed bytes of the image in. + The InflaterInputStream that contains the deflated(compressed) image. + + + + Does the remaining work to convert the byte[] containing the image into a Color[] containing colors + for each pixel. This operation is different for most individual combinations of ColorType and bitDepth + and currently only ColorType 2 is supported. + + + + + + + + + Loads a .png image from the fileName given and returns an array of pixel colors. + + The name of the .png image to be loaded. + + Returns a Microsoft.Xna.Framework.Graphics.Color[] containing one item(Color) for each pixel + in the image. + + + + Binds common .png chunk types to their Hexadecimal values. + + + + + Represents a typed chunk of data from the .png image. + + + + + Holds all of the information contained in the IHDR chunk of the .png image. + + + + + Downloads a file locally + + The remote file to download. For example: "ftp://ftp.flatredball.com/flatredball.com/"; + The local file name to download to + FTP username + FTP password + + + Note: this class will box value types, use only to store system state to allow recovery, + not in-game state while in a loop. + + + Casts the key value to "T". + default(T) if the value is not found in state manager. + + + should be after events are subscribed to. + + + Raises the Activating event. + + + Raises the Deactivating event. + + + This event is raised when the game is being resumed after being deactivated. + + + This event is raised when state should be saved. + + + Will be true of the Activating event was raised. + + + Returns the key value, null if not found + + + Singleton instance + + + Simple implementation ... keeps state in memory, does not save anywhere. + Also, Activated and Deactivated events will never raise. + This implementation is for cross platform support on non-windows phone platforms + + + + A Save class storing a BodySprite. + + + This class is used in the SpriteRigSave class. + + + + + A Texture2D and relative integer position. + + + This struct is generally used to detect patterns on FlatRedBall.ManagedSpriteGroups.SpriteGrids. + + + + + Creates a new GridRelativeTexture. + + The relative X position to assign. + The relative Y position to assign. + The Texture2D reference to assign. + + + + An XML-Serializbale class representing the state of a Joint. Used in + the SpriteRigSave class. + + + + + A snapshot of a set of rotations, scales, and positions of Sprites in a SpriteRig. + + + These poses are used as keyframes for SpriteRig animation. SpriteRigs interpolate + between poses to create fluid animation. + + + + + Creates a new Pose with an empty but instantiated jointStateArray and bodySpriteStateArray. + + + + + Creates a new Pose with the argument name, jointStateArray, bodySpriteStateArray, + and time (in milliseconds) from the beginning of the PoseChain. + + Name of the pose. + Reference to the JointStateArray to use. + Reference to the BodySpriteStateArray to use. + Time in milliseconds that the Pose should be set from the beginning of the PoseChain. + + + + Determines whether this instance differs from the argument Pose. + + + This only tests the stored BodySpriteStates and JointStates. This method + does not compare the time and name variables. + + The Pose to compare against. + Whether this instance and the argument BodySpriteStates or PoseStates differ. + + + + Set's a particular Pose's poseStateArray, bodySpriteStateArray, and time to the passed argument's corresponding variables. + + + This method does not change the name of the Pose. It will remain the same after the method is called. + + The pose from which to copy the information. This is a shallow copy - members are not cloned, but + rather references are copied. + + + + Creates a clone of this instance. + + + The cloned Pose will have its own JointStateArray and BodySpriteState array, but each will + have the same values. + + Reference to the cloned Pose. + + + + The name of the Pose. + + + + + The state of all of the joints (rotation and relative position) for this pose. + + + + + The state of all of the body Sprites (relative position, scale, and frame) for this pose. + + + + + The number of seconds that the pose should be executed from the beginning of the PoseChain + + + + + Represents a collection of Poses which can be interpolated between to create + fluid SpriteRig animation. + + + + + The color operation to use when applying the Red, Green, and Blue properties. + + + + + Controls how the SpriteChain will blend with objects behind it when drawn. + + + + + The red component to use when applying the ColorOperation. + + + + + The green component to use when applying the ColorOperation. + + + + + The blue component to use when applying the ColorOperation. + + + + + The alpha (opacity) to use when rendering the SpriteChain. + + + + + The rate of change (in units per second) to apply to the Red property. + + + + + The rate of change (in units per second) to apply to the Green property. + + + + + The rate of change (in units per second) to apply to the Glue property. + + + + + The rate of change (in units per second) to apply to the Alpha property. + + + + + Delegate for methods which can be assigned to the SpriteFrame + for every-frame custom logic. + + + + + The SpriteFrame on which the logic should execute. + + + + Visible object with static-width borders. + + + SpriteFrames are often used for creating UI because their static-width + borders make single-texture UI entities easy to construct and manage. + + SpriteFrames achieve a static-width border by + + + + + + + Constructs a new, empty SpriteFrame. + + + + + Constructs a new SpriteFrame using the argument texture and border sides. + + + SpriteFrames are usually created through the SpriteManager's AddSpriteFrame method. + + + The texture that the SpriteFrame will display. + Which sides should be used by the SpriteFrame. + + + + Constructs a new SpriteFrame using the argument texture, border sides, and Layer. + + The texture that the SpriteFrame will display. + Which sides should be used by the SpriteFrame. + The Layer that the SpriteFrame will be drawn on. + + + + Creates a copy of the SpriteFrame instance. + + + The cloned SpriteFrame will not belong to any of the lists that the original does. + Since it will not be added to the SpriteManager it will not be drawn and managed. + To add the SpriteFrame to the engine, call SpriteManager.AddSpriteFrame passing + the newly created SpriteFrame as the argument. + + + The newly-created SpriteFrame. + + + + Returns whether a ray starting at the argument Camera's position and travelling through the + point relative to the Camera specified by the arguments intersects with this instance. + + + This method does not take the camera's rotation into consideration when calculating the ray. + + The X position relative to the argument Camera. + The Y position relative to the argument Camera. + The Z position relative to the argument Camera. + The camera to use for the intersection test. + Whether the ray intersects the SpriteFrame. + + + + Returns whether the argument Sprite is a Sprite used by this instance. + + The Sprite to check. + Whether the argument Sprite is a component of this. + + + + Stops all automatic behavior and stores the necessary instructions to + resume activity in the argument InstructionList. + + The List to store instructions which are executed to + resume activity. + + + + Returns a string with information about this instance. + + The string containing information about this instance. + + + + Sets the contained DynamicSpriteFrame to help identify SpriteFrame membership and side. + + + If the SpriteFrame's name is "spriteFrame1", each side will have its name be + the SpriteFrame's name with a suffix indicating the side that the Sprite represents. + That is, the center Sprite would be named "spriteFrame1_center", the top + would be "spriteFrame1_top", and so on. + + This method can be used in debugging to help identify whether DynamicSprites belong + to a SpriteFrame, and if so, which side they represent. Otherwise, this + method has no engine functionality. + + + + + Performs the necessary every-frame management of the SpriteFrame. This + method is automatically called by the SpriteManager. + + + + + Updates the SpriteFrame borders. This method is called automatically + whenever the Borders property is changed. + + The new BorderSides to use. + + + + Every-frame automatically raised by the SpriteManager. + + + + + Controls the SpriteFrame's transparency. + + + + + The rate of change of the alpha component in units per second. + + + + + The red value to use with the ColorOperation. + + + + + The green value to use with the ColorOperation. + + + + + The blue value to use with the color operation. + + + + + The rate of change of the red component in units per second. + + + + + The rate of change of the green component in units per second. + + + + + The rate of change of the blue component in units per second. + + + + + The color operation to perform using the color component values and + Texture (if available). + + + + + The blend operation to perform using the alpha component value and + Texture (if available). + + + + + Whether animation is currently turned on. + + + + + Gets all animations stored in this. + + + + + Gets and sets how fast AnimationChains will animate. Default is 1. A value + of 2 will result in AnimationChains animating twice as fast. + + + + + Gets the current AnimationChain. + + + + + Gets and sets the index of the current AnimationChain. + + + + + Gets the current AnimationChain name or sets the current AnimationChain by name. + + + Setting this property will set the search the SpriteFrame for an AnimationChain with a + matching name and set it as the current AnimationChain. + + + + + Gets and sets the current AnimationFrame index. + + + + + Gets whether the current AnimationFrame just changed this frame due to animation. + + + + + Gets whether the current AnimationChain just cycled (looped) this frame due to animation. + + + + + Whether the current AnimationFrame's relative position values (RelativeX and RelativeY) are applied + when animating. + + + + + The Layer that this SpriteFrame belongs to. + + + + + The width of the border in texture coordinates. + + + This defines the section of the texture that should not stretch. Increasing this value will + show more of the texture on the outside border Sprites and less on the inside. + + + + + The width of the border Sprites in world coordinates. + + + + + The borders that the SpriteFrame uses to display. + + + + + The X size of the object. Measured as the distance from the center of the SpriteFrame + to its left and right edges in world coordinates. + + + + + The Y size of the SpriteFrame. Measured as the distance from the center of the SpriteFrame + to its top or bottom edges in world coordinates. + + + + + The rate of change of the ScaleX property in units per second. Default 0. + + + + + The rate of change of the ScaleY property in units per second. Default 0. + + + + + Whether the SpriteFrame is drawn. + + + + + The texture to be displayed by the SpriteFrame. + + + + + Whether the instance can be selected by the Cursor. + + + + + Gets the SpriteFrame's center Sprite. + + + This can be used to modify the SpriteFrame's appearance. + + + + + Defines sides which can be combined to speicfy borders on SpriteFrames. + + + The most common combinations are BorderSides.All, + BorderSides.Left | BorderSides.Right, and BorderSides.Top | BorderSides.Bottom. + + + + + No border sides - the SpriteFrame will appear similar to a regular Sprite. + + + + + Include a border on the top of the SpriteFrame. + + + + + Include a border on the bottom of the SpriteFrame. + + + + + Include a border on the top and bottom of the SpriteFrame. This is equivalent to + BorderSides.Top | BorderSides.Bottom + + + + + Include a border on the left of the SpriteFrame. + + + + + Include a border on the right of the SpriteFrame. + + + + + Include a border on the left and right of the SpriteFrame. This is equivalent to + BorderSides.Right | BorderSides.Left + + + + + Include a border on the top, left, and right of the SpriteFrame. This is + equivalent to BorderSides.Left | BorderSides.Right | borderSides.Top. + + + + + Include borders on all sides of the SpriteFrame. This is equivalent to + BorderSides.Right | BorderSides.Left | BorderSides.Top | BorderSides.Bottom. + + + + + Creates a SpriteGrid using the TextManager's DefaultFont. This is used because it's the only + Texture2D stored internally in the engine. To set the default Texture, use the overload which + accepts a Texture2D. + + + + + Creates a new SpriteGrid. + + + The mBlueprintToUse argument Sprite reference is kept internally and used as the blue print. + In other words, the SpriteGrid does not create a new Sprite internally, but uses the + arguemnt Sprite. When creating SpriteGrids in code, it is common to create a new Sprite + only to serve as a SpriteGrid blue print, then remove it from the SpriteManager's memory. + If the Sprite passed as the mBlueprintToUse is modified after the SpriteGrid is created, + this will change the SpriteGrid bluerint + + + Reference to the camnera used to determine whether a point on the SpriteGrid is in the scren. + Whether the SpriteGrid should extend on the XY or XZ plane. + Reference to a Sprite representing the mBlueprint to be used for the grid. + + + + Destroys the SpriteGrid by removing all contained Sprites and clearing the TextureGrid. + + + This method will only remove all contained Sprites from the SpriteManager and clear out the + TextureGrid. The SpriteGrid will still reference the the blueprint + Sprite and have the same Bounds and GridSpacing. + If the Manage method is called after this method is called, + the SpriteGrid will throw an out of bounds exception. To refill the + SpriteGrid after this method has been called, it must first be populated. + + + + + + + + + + + The old FrbTexture at the argument position. + + + + Clears all Sprites referenced by the SpriteGrid. + + + This method will only remove all contained Sprites from the SpriteManager. This method does + not clear out the TextureGrid. + + If the Manage method is called after this method is called, + the SpriteGrid will throw an out of bounds exception. To refill the + SpriteGrid after this method has been called, it must first be populated. + + + + + Moves the grid by the passed variables + + + This method does not change the bounds of the SpriteGrid; only the actual Sprites in the grid. + This method is used to change the seed position of the SpriteGrid. The location of the + painted Sprites also shifts according to the arguments. + + + + + This is called by CreateSpriteFromBlueprint - and should only be called from there. + + The newly created Sprite which was created obeying the SpriteGrid's properties. + + + + The layer on which the SpriteGrid should place newly-created Sprites. + + + + + Add clone method + Author: Chakkapun Singto-ngam, Extend Interactive Co.,Ltd. + + + + + Sets the pose to the argument value over tweenLengthInSeconds time + + + This method sets the pose of the SpriteRig given a set of JointStates and BodySpriteStates. If the + pose is to be set immediately, then tweenLengthInSeconds should be 0. If the pose should be transitioned + to over a given amount of time, then tweenLengthInSeconds represents how long it should take to transition + to the argument pose values. + + The joint states to transition to. + The bodySpriteStates to transition to. + How much time to take to transition to these values. + Whether to use instructions or not. Use instructions if you want the SpiteRig to stop after reaching + its desired state. Setting this to true allocates some memory, and this is unnecessary if you plan on setting a new pose chanin + after this operation finishes. + + + + Performs Animation logic. This is called by the Manage() method. + + + + + Stops all velocities related to animation and removes all instructions used for animation. Does not set the animate property to false. + + + This function stops the following: + - Joint relative velocity + - Joint RotationZVelocity + - BodySprite scale velocity + - BodySprite relative velocity + This function is used to stop animation activity on joints which may be ignored + in the current PoseChain. Simply setting animate to false will not necessarily stop + all activity as this only stops animation affected by the current PoseChain. + + + + + Gets and sets the bool determining whether the SpriteRig should animate. + + + Insert remarks here. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + + + The Sprite which is often used to move the SpriteRig. The root should + be the topParent of any Sprite in the SpriteRig unless it is attached to + another Sprite which is not part of the SpriteRig. + + + The root does not belong to either the mJoints or bodySprites SpriteArray; + however, when the SpriteRig is saved, the root is included in the bodySprite array. + + + + + Sets how fast the SpriteRig animates. + + + Setting the animationSpeed to 0 sets the animate bool to 0. Setting the animationSpeed to any value + except 0 sets the animate bool to true. + + + + + + Shifts the position of all textures in the TextureGrid. + + The distance along the x axis to shift the grid. + The distance along the y axis to shift the grid. + + + + This "shrinks" the grid if its edges are the same as its baseTexture + + + To use the least amount of memory, TextureGrids only store non-baseTexture strips. + If the ends of a horizontal strip are the baseTexture, then the strip should be contracted + inward. If an entire horizontal strip is the baseTexture, then it should be removed. + + Usually, tests should begin from a specific location, as it is usually called after the + grid is painted. This method will first check to see if the arguments are on the left or + right side of a strip. Then a loop will move inward as long as it continues to encounter + the base texture. Once it encounters a non-baseTexture, then it stops, and reduces the + particular horizontal strip. + + If an entire strip is cleared and it is either the top or bottom strip, then it will + be removed, and the strip above or below (depending on position) will be tested as well. + If the strip is in the center (not the top or bottom), then it will be reduced, but cannot + be removed. + + The y location in absolute coordinates to start the tests at. + The x location in absolute coordinates to start the tests at. + + + + This "shrinks" the grid if its edges are the same as its baseTexture + + + To use the least amount of memory, TextureGrids only store non-baseTexture strips. + If the ends of a horizontal strip are the baseTexture, then the strip should be contracted + inward. If an entire horizontal strip is the baseTexture, then it should be removed. + + Usually, tests should begin from a specific location, as it is usually called after the + grid is painted. This method will first check to see if the arguments are on the left or + right side of a strip. Then a loop will move inward as long as it continues to encounter + the base texture. Once it encounters a non-baseTexture, then it stops, and reduces the + particular horizontal strip. + + If an entire strip is cleared and it is either the top or bottom strip, then it will + be removed, and the strip above or below (depending on position) will be tested as well. + If the strip is in the center (not the top or bottom), then it will be reduced, but cannot + be removed. + + The y index start the tests at. + The x index to start the tests at. + + + + Checks the boundaries of the grid and removes any references to textures that match the base Texture2D. + + + This method is called automatically by the ReplaceTexture method so that the structure + stays as small as possible afterchanges have been made. + + + + + Used to paint SpriteGrids or compare differences between two TextureGrids which can be used for undos. + + The type contained in the TextureLocation. Currently FlatRedBall uses + Texture2D, FloatRectangle, and AnimationChain in SpriteGrids. + + + + A class storing enumerations for collisions. + + + + + Creates a height map from a texture + + The path to the texture + + + + + Creates a height map from a model (heightmap will be available next frame) + + + The desired width of the heightmap (resolution) + The desired height of the heightmap (resolution) + The model to render for the heightmap + + + + Creates a height map from a model (heightmap will be available next frame) + + The desired width of the heightmap (resolution) + The desired height of the heightmap (resolution) + The model to render for the heightmap + + + + Whether or not this heightmap has finished initialization + + + + + Gets a height value at a specified coordinate + + The x coordinate + The y coordinate + The z value + + + + Gets the raw data for this heightmap + + + + + Returns whether this instance collides against the argument Circle. + + The Circle to test collision against. + Whether collision has occurred. + + + + Returns whether this instance collides against the argument AxisAlignedRectangle. + + The AxisAlignedRectangle to test collision against. + Whether collision has occurred. + + + + Returns whether this instance collides against the argument Polygon. + + The Polygon to test collision against. + Whether collision has occurred. + + + + Returns whether this instance collides against the argument Line. + + The Line to test collision against. + Whether collision has occurred. + + + + Returns whether this instance collides against the argument Capsule2D. + + The Capsule2D to test collision against. + Whether collision has occurred. + + + + Returns whether this instance collides against the argument ShapeCollection. + + The ShapeCollection to test collision against. + Whether collision has occurred. + + + + Returns the distance from this to the argument Line. If this and the Line + are colliding, then the value will be 0 or negative. The smallest the + return value can be is -this.Radius. + + The line to test distance from. + The distance from the circle to the argument line. Will be 0 or + negative if the two are colliding. + + + + Adjusts the calling Circle's position (or its parent if attached) so that the circle is fully-contained + in the argument AxisAlignedRectangle. + + The rectangle to keep the circle inside of. + + + + Returns the tangent (in other words the vector parallel with the surface) where the last collision occurred. + + + + + A rectangle class using floats for its bounds. + + + + + A Rectangle with its top-left point at (0,0) with a width and height of 1. + + + + + A drawable, 3D volume defining the visible area of a camera. This can be used to + display the visible area of a Camera. + + + + + Represents a segment with defined endpoints which can be used to + graphically display lines or to perform segment collision. + + + + + The first point used to define the line. This point is relative to the Line's position and rotation. + + If a line moves or rotates it will visibly change on screen and its collisions will be modified, but the + RelativePoint fields will not be modified. + + + + + + The second point used to define the line. This point is relative to the Line's position and rotation. + + + If a line moves or rotates it will visibly change on screen and its collisions will be modifed, but the + RelativePoint fields will not be modified. + + + + + Instantiates a new Line. + + + Lines created through this method will not be visible or managed. To add management, add + the line to the ShapeManager through the ShapeManager's AddLine method. + + + + + Returns a Segment which represents the line. + + The Segment representing the line. + + + + Returns whether this instance collides with the argument Circle. + + The Circle to test collision against. + Whether a collision has occurred. + + + + Returns whether this instance collides with the argument AxisAlignedRectangle. + + The AxisAlignedRectangle to test collision against. + Whether a collision has occurred. + + + + Returns whether this instance collides with the argument Line. + + The Line to test collision against. + Whether a collision has occurred. + + + + Returns whether this instance collides with the argument Polygon. + + The Polygon to test collision against. + Whether a collision has occurred. + + + + Returns whether this instance collides with the argument Circle. The two + shapes are also repositioned so they do not collide after the method is called. + + The Circle to test collision against. + The mass of this instance. + The mass of the other instance. + Whether a collision has occurred. + + + + Scales the line by a specified amount + + The scaling factor + + + + Sets the Line's Position to the average of the two given endpoints and + it's RelativePoints to be the distance from the new Position to each of the given Vectors. + + The first endpoint for which to find the midpoint + The second endpoint for which to find the midpoint + + + + Returns the absolute position of the last collision point. This will not + necessarily return the intersection point of the line with the last collided + shape. It may return a point inside the shape. + + + + + Gets or sets the visibility of this line segment + + + + + Gets or sets the color of this line segment, when drawn + + + + + The points relative to the center of the Polygon. These + define the shape and size of the Polygon. + + + + + The midpoint Points used for collision. These are updated in CollideAgainstMovePreview. + + + + + Returns an equilateral shape of numberOfSides sides. + + + The newly-created Polygon is invisible and is not part of the ShapeManager. + + The number of sides of the Polygon. Must be at least 3. + The angle relative to the polygon of the first point. + The newly-created Polygon. + + + + Returns whether two indexes are adjacent. This considers wrapping and duplicate + points for closed polygons. + + The first index. + The second index + Whether the two points are adjacent. + + + + Returns a Vector3 array storing all of the points belonging to this instance that are inside the argument + Polygon. + + + + + + + Returns the relative position of the point at the argument index. + + The index of the point to get. + The point at the argument index. + + + + Inserts a new point at the given index. The point will be inserted at object space. + + + This method recreates the internal point list so it is expensive to call repeatedly. + + Index where the point should be inserted. + The (object space) point to insert. + + + + Returns whether the argument vector is in this polygon. + + The position of the point + Whether the point is inside. + + + + Scales all points in object space. + + Amount to scale by on the object's X axis. + Amount to scale by on the object's Y axis. + + + + Changes the position of the point at argument index, recalculates the bounding radius, and raises the + OnPointsChanged event. + + The index of the point to change. + The new X position of the point in polygon object space. + The new Y position of the point in polygon object space. + + + + Returns a vector from the argument vector to the closest point on this. + + The point to start from. + A vector representing the distance from the argument vector to this. + + + + Returns a vector from the argument point to the closest point on this Polygon. + + The absolute X position of the point to begin the vector at. + The absolute Y position of the point to begin the vector at. + The shortest vector from the argument x,y to the Polygon. + + + + The Position-relative points of the Polygon. + + + + + Reports the vector along which this polygon was moved along during the last + CollideAgainstMove method. + + + This value is reset every time CollideAgainstMove is called whether there is a + successful collision or not. If there is no collision, this value is set to + Vector3.Zero. If reactions to collisions such as physics are being implemented + using this value, then the behavior should be tested and applied after every + call to CollideAgainstMove. + + This value is set on both the instance calling the CollideAgainstMove method + as well as the argument. + + + + + + The absolute position where the last collision was detected in a CollieAgainst method. + + + + + Event raised when the Points property reference is reset or when the + SetPoint method is called. + + + + + The first point of the segment. + + + + + The second point of the segment. + + + + + Creates a new Segment with the argument points as the endpoints. + + The first Point. + The second Point. + + + + Returns the distance to the argument point as well as + the connectin Vector3 from the Point to this. + + The point to get the distance to. + The connecting vector from the argument Pointn to this. + The distance between this and the argument Point. + + + + Returns the length of the segment. + + + + + Determines whether the closest point on the segment lies on one of the endpoints. + + The point to test to. + Whether the closest point on this segment to the argument point lies on the endpoints. + + + + Determines whether this segment intersects the argument segment. + + The segment to test for intersection. + Whether the segments intersect (whether they cross). + + + + Returns the point where this segment intersects the argument segment. + + The segment to test for intersection. + The point where this segment intersects the + argument segment. If the two segments do not touch, the point + will have both values be double.NAN. + + + + + Shifts the segment by moving both points by the argument x,y values. + + The number of units to shift the segment by on the x axis. + The number of units to shift the segment by on the y axis. + + + + Sets the length of the segment to 1 unit by moving the 2nd point. + + + If the segment has 0 length (the endpoints are equal), the method + does not change the segment; length will remain 0. + + + + + Returns the geometric slope of the segment. + + + + + Returns the y intercept of the slope. + + + This method treats the segment as a line, so this will return a value even + though the segment may not cross the x=0 line. + + + + + Makes all contained lists (such as for AxisAlignedRectangles and Circles) two-way. + + + + + Makes all contained lists (such as for AxisAlignedRectangles and Circles) one-way. + + + + + Moves all contained objects by the argument shiftVector; + + The amount to shift by. + + + + Checks if the designated 2D point is in the 2D shapes of the shape collection + + + + + Responsible for creating, destroying, and managing shapes (Circle, AxisAlignedRectangle, + Polygon, Line). + + + + + The number of vertices used when drawing a Circle. + + + + + The number of vertices used when drawing Capsule2Ds. + + + + + The number of vertices used when drawing an AxisAlignedCube. + + + + + The number of vertices used when drawing a Sphere. + + + + + List of all managed objects. This list contains all types of shapes + (Circles, Polygons, etc). This list is only used for the TimedActivity; + not for drawing. Therefore, shapes can exist both in this list as well as + in the type-specific lists (mCircles, mPolygons, etc). + + + + + Creates and returns a new visible, managed AxisAlignedRectangle. + + + The new AxisAlignedRectangle will be visible, white, and have a ScaleX and ScaleY of 1. + + The new AxisAlignedRectangle. + + + + Adds an already-created AxisAlignedRectangle to the ShapeManager. + The newly-added AxisAlignedRectangle will be made visible by this method. + + The AxisAlignedRectangle to add. + The same AxisAlignedRectangle as was passed to the method. + + + + Adds all AxisAlignedRectangles contained in the argument axisAlignedRectangleList to the ShapeManager. + + The list containing the AxisAlignedRectangles. + + + + Adds and returns a new visible, managed Capsule2D. + + + The new Capsule2D will be visible, white, and have an EndpointRadius of 1. + + The new Capsule2D. + + + + Adds an already-created Capsule2D to the ShapeManager. + The newly-added Capsule2D will be made visible by this method. + + The Capsule2D to add. + The instance that was just added. + + + + Adds and returns a new visible, managed Circle. + + + The new Circle will be visible, white, and have a Radius of 1. + + The new Circle. + + + + Adds an already-created Circle to the ShapeManager. + The newly-added Circle will be made visible by this method. + + The Circle to add. + The instance that was just added. + + + + Adds all Circles contained in the argument circleList to the ShapeManager. + + The list containing the Circles. + + + + Adds and returns a new visible, managed Sphere. + + + The new Sphere will be visible, white, and have a Radius of 1. + + The new Sphere. + + + + Adds an already-created Sphere to the ShapeManager. + The newly-added Sphere will be made visible by this method. + + The Sphere to add. + The instance that was just added. + + + + Adds a new visible, managed AxisAlignedCube. + + The new AxisAlignedCube + + + + Adds an already-created AxisAlignedCube to the ShapeManager. + The newly-added AxisAlignedCube will be made visible by this method. + + The AxisAlignedCube to add. + The instance that was just added. + + + + Adds a new 0-point Polygon to the ShapeManager. + The newly-added Polygon must have its Points property + set to be visible and functional. + + The new Polygon. + + + + Adds an already-created Polygon to the ShapeManager. + The newly-added Polygon will be made visible by this method + if it has any points. + + The Polygon to add. + The instance that was just added. + + + + Adds all Polygons contained in the argument polygonList to the ShapeManager. + + The list containing the Polygons. + + + + Adds and returns a new visible, managed Line. + + + The new Line will be visible, white, horizontal, + and have a length of 2 units. + + The new Line. + + + + Adds an already-created Line to the ShapeManager. + The newly-added Line will be made visible by this method. + + The Line to add. + The instance that was just added. + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Brings the passed in Shape to the front so it's drawn on top. + + + + + Removes the argument AxisAlignedRectangle from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The AxisAlignedRectangle to remove. Cannot be null. + + + + Removes the argument AxisAlignedCube from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The AxisAlignedCube to remove. Cannot be null. + + + + Removes the argument Capsule2D from the ShapeManager and any 2-way + PositionedObjectLists it belongs to. + + The Capsule2D to remove. Should not be null. + + + + Removes the argument Circle from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The Circle to remove. Should not be null. + + + + Removes the argument Sphere from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The Sphere to remove. Should not be null. + + + + Removes the argument Polygon from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The Polygon to remove. Should not be null. + + + + Removes the argument Line from the ShapeManager and any 2-way PositionedObjectLists it belongs to. + + The Line to remove. Should not be null. + + + + Removes the argument Polygon from the ShapeManager. + + The Polygon to remove. Should not be null. + + + + Removes the argument AxisAlignedRectangle from the ShapeManager. + + The AxisAlignedRectangle to remove. Cannot be null. + + + + Removes all Polygons held in the argument listToRemove from the Shapemanager and any 2-way PositionedObjectLists they belong to. + + The type of object which must be a Polygon. + The list of objects to remove. + + + + Returns information about the ShapeManager. + + A string containing information about the ShapeManager. + + + + Controls whether the ZBuffer is tested against when drawing shapes. + Set to false to have Shapes drawn on top. + + + + + A read-only list of visible Circles contained in the ShapeManager. + + + + + A read-only list of visible AxisAlignedRectangles contained in the ShapeManager. + + + + + A read-only list of visible Polygons contained in the ShapeManager. + + + + + A read-only list of visible Lines contained in the ShapeManager. + + + + + A read-only list of visible Spheres contained in the ShapeManager. + + + + + A read-only list of visible AxisAlignedCubes contained in the ShapeManager. + + + + + A read-only list of visible Capsules contained in the ShapeManager. + + + + + A read-only list of shapes updated by the ShapeManager. + + + + + Modifies the first Polygon so that it is the result of both merged polygons. + This method assumes that the polygons collide and that both are drawn + clockwise. + + The first polygon. This one will be modified. + The second polygon which will not be modified. + + + + Pauses this instance and stores the unpause instructions in the argument InstructionList + + The list to fill + + + + Applies the every-frame activity for moving the object. This is automatically called every frame by the engine if this object is part of the ShapeManager. + + The number of seconds that have passed since last frame + SecondDifference * secondDifference / 2 - used for integrating acceleration. + How much time passed last frame. + + + + Constructs a BoundingSphere instance form this. + + The created instance + + + + Returns whether this instance overlaps the argument Sphere. + + The other Sphere to test against. + Whether collision has occurred. + + + + Collision method that returns whether collision has occurred and repositions this and the + argument Sphere to prevent overlap. + + The other Sphere to collide against. + The mass of the calling Sphere. This value is used relative to "otherMass". Both cannot be 0. + The mass of the argument Sphere. This value is used relative to "thisMass". Both cannot be 0. + Whether the calling Sphere and the argument Sphere are touching. + + + + Returns whether this instance overlaps the argument AxisAlignedCube + + The instance AxisAlignedCube to test against. + Whether collision has occurred + + + + Returns whether this instance overlaps the argument cube, and separates the two instances according to their relative masses. + + The cube to perform collision against. + The mass of this instance. + The mass of the cube. + Whether the objects were overlapping before the reposition. + + + + Performs a bounce collision (a collision which modifies velocity and acceleration), and separates the objects if so. + + The cube to perform collision against. + The mass of this instance. + Th e mass of the argument cube. + The ratio of velocity to preserve. + Whether a collision occurred. + + + + Performs a bounce collision (a collision which modifies velocity and acceleration), and separates the objects if so. + + The Sphere to perform collision against. + The mass of this instance. + Th e mass of the argument cube. + The ratio of velocity to preserve. + Whether a collision occurred. + + + + Returns whether the argument point is inside this instance. + + The point in world coordinates. + Whether the point is inside this. + + + + Returns whether the argument cursor is over this instance. + + The cursor to check. + + + + + Returns whether the argument cursor is over this instance considering Layer coordinates. + + The cursor to check. + The layer that this instance sits on. + Whether the mouse is over this. + + + + The Sphere's Color to use when being rendered. + + + + + The rate at which the velocity is increasing - this is 0 by default. + + + + + Whether this instance is visible. Setting this to true will add this instance to the ShapeManager's drawn list. + + + + + Determines the shortest absolute difference between two angles. + + + This method will never return a value with absolute value greater than PI. It will return + either a positive or negative value, keeping all values between positive and negative PI. + + Starting angle in radians. + Ending angle in radians. + The number of radians between the two angles. + + + + Extracts the RotationX, RotationY, and RotationZ values out of a + + The matrix to get the rotation values out of. + The RotationX of the Matrix. + The RotationY of the Matrix. + The RotationZ of the Matrix. + + + + Determines the shortest absolute difference between two frames. + + + This method will consider moving forward and backward, as well as cycling from the end + to the beginning of an AnimationChain. + + The animationChain to use when determining the distance. + The first frame. + the second frame. + + + + + Returns a random point on the surface of a unit sphere. + + Reference to a Random instance. + The resulting X value. + The resulting Y value. + The resulting Z value. + + + + Keeps an angle between 0 and 2*PI. + + The angle to regulate. + + + + Rotates a point around another point by a given number of radians. + + X position to rotate around. + Y position to rotate around. + X position to rotate (changes). + Y position to rotate (changes). + Radians to rotate by. + + + + Rotates a Point around another Point by a given number of radians. + + Point to rotate around. + Point to rotate (changes position). + Radians to rotate by. + + + + Wraps an angle from 0 to TwoPi + + The angle to wrap + The new angle + + + + A class which can be used to store and calculate rolling averages for regular numbers and radians. + + + + + Creates a new RolllingAverage with capacity equalling the argument capacity value. + + The maximum number of values that the RollingAverage can store. + + + + Adds a value to the RollingAverage. The oldest value is discarded if the Capacity has been reached. + + The value to add. + + + + Gets the rolling average using the values currently stored. + + + The rolling average is calculated using the Capacity number of + values. If AddValue has not been called enough times to fill the + Capacity, then the number of values stored are used. + + + + + Gets the number of values that are used when calculating a rolling average. + + + + + Gets and sets whether the average value is calculated as radians. + + + This is important for radian values because rotation values reported + by FlatRedBall loop every 2*PI + + + + + A List of Splines. This inherits from a generic List of Splines, but provides + extra functionality. This is the runtime type for the .splx format. + + + + + Instantiates an empty SplineList + + + + + Instantiates a SplineList and sets its capacity. + + + + + + Instantiates a SplineList and populates it with the Splines contained in the argument IEnumerable. + + The Splines to add to the newly-created SplineList. + + + + Searches for and returns the first Spline with the name matching the argument, or + null if no matches are found. + + The name to search for. + The contained Spline with the matching name, or null. + + + + The name of the SplineList. Can be used to provide debug + and identification information. + + + + + A point in a Spline storing position, velocity, acceleration, and time information. + + + + + The position of the SplinePoint in absolute world coordinates. + + + + + The velocity of an object as it passes through this SplinePoint when moving along a Spline. + + + + + The acceleration set when passing through this SplinePoint. This property is usually + automatically set by the containing Spline. + + + + + The time relative to the start of the Spline when an object moving through the Spline + will pass through this point. + + + + + Controls whether the Velocity value is unchanged by calling + CalculateVelocities on the Spline. By default this is false, + which means velocity on this SplinePoint will be set according + to the position of the neighboring SplinePoints. If this value + is true, then the velocity on this will not be changed by Spline.CalculateVelocities. + + + + + A class which can be used to calculate a best-fit line given a set of points. + + + + + Returns a float value between 0 and the argument maxValue; + + The random instance to use. + The max value to return. + A value between 0 and the max value. + + + + Summary description for NetSprite. + + + + + [Undocumented] + + + + + The amount of time in seconds that the NetworkManager will hold information before sending it. + This is used to help simulate lag for debugging. + + + + + A strongly-typed resource class, for looking up localized strings, etc. + + + + + Returns the cached ResourceManager instance used by this class. + + + + + Overrides the current thread's CurrentUICulture property for all + resource lookups using this strongly typed resource class. + + + + + Looks up a localized string similar to info face="Arial" size=16 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 + common lineHeight=16 base=13 scaleW=256 scaleH=256 pages=1 packed=0 + page id=0 file="defaultFont_00.tga" + chars count=319 + char id=32 x=0 y=0 width=1 height=0 xoffset=0 yoffset=16 xadvance=4 page=0 chnl=0 + char id=33 x=227 y=74 width=1 height=10 xoffset=1 yoffset=3 xadvance=3 page=0 chnl=0 + char id=34 x=54 y=97 width=3 h [rest of string was truncated]";. + + + + Tells the screen that we are done and wish to move to the + supplied screen + Fully Qualified Type of the screen to move to + + + This should be a valid enum value of the concrete screen type. + + + Default implementation tells the screen manager to finish this screen's activity and navigate + to the previous screen on the backstack. + Override this method if you want to have custom behavior when the back button is pressed. + + + + Returns how much time the Screen has spent paused + + + + + Gets and sets whether the activity is finished for a particular screen. + + + If activity is finished, then the ScreenManager or parent + screen (if the screen is a popup) knows to destroy the screen + and loads the NextScreen class. + + + + The fully qualified path of the Screen-inheriting class that this screen is + to link to. + + + This property is read by the ScreenManager when IsActivityFinished is + set to true. Therefore, this must always be set to some value before + or in the same frame as when IsActivityFinished is set to true. + + + + + Calls activity on the current screen and checks to see if screen + activity is finished. If activity is finished, the current Screen's + NextScreen is loaded. + + + + + Loads a screen. Should only be called once during initialization. + + Qualified name of the class to load. + + + Do all state deactivation work here. + + + Do all state activation work here + + + + Used by particles to flag a particular Sprite as empty. + + + The SpriteManager is responsible for particle recycling and uses this value to indicate + whether a Sprite is currently used. This should not be manually modified. + + + + + Performs the every-frame logic for updating the current AnimationFrame index. If the + Sprite is part of the SpriteManager then this is automatically called. + + The number of seconds that have passed since the game has started running. + + + + Clears all references to AnimationChains and sets the Animate property to false. + + + + + Removes the AnimationChain from the Sprite's internal AnimationChain List. + + + If the chainToRemove is also the CurrentChain, the animate field + is set to false. + + The AnimationChain to remove. + + + + Sets the argument chainToSet as the animationChain. If the argument chainToSet is not + part of the Sprite's internal list of AnimationChains, it is added. + + + This differs from FlatRedBall MDX - this method on FlatRedBall MDX does not add the argument + AnimationChain to the Sprite's internal list. + + This does not set any animation-related properties, but it does set the current + texture to the current frame's texture. Therefore, it is still necessary to set Animate to true. + + + The AnimationChain to set as the current AnimationChain. This is + added to the internal AnimationChains property if it is not already there. + + + + Sets the current AnimationChain by name and keeps the CurrentFrame the same. + + + This method assumes that the Sprite contains a reference to an AnimationChain with the name matching chainToSet. Passing a + name that is not found in the Sprite's AnimationChainArray will not cause any changes. + + This method will keep the CurrentFrame property the same (unless it exceeds the bounds of the new AnimationChain). In the + case that the CurrentFrame is greater than the bounds of the new AnimationChain, the animation will cycle back to the beginning. + The animate field is not changed to true if it is false. + + + The name of the AnimationChain to set as current. + + + + Sets the ScaleY so that the ScaleX/ScaleY ratio is the same as the source image used for the Sprite's texture. + + + + + Sets the ScaleY so that the ScaleX/ScaleY ratio is the same as the source image used for the Sprite's texture. + + + + + Applies all velocities, rates, accelerations for real and relative values. + If the Sprite is part of the SpriteManager (which is common) then this is automatically + called. + + The number of seocnds that have passed since last frame. + Precalculated (secondDifference * secondDifference)/2.0f for applying acceleration. + The number of seconds that passed last frame for calculating "real" values. + + + + Returns a clone of this instance. + + + Attachments are not cloned. The new clone + will not have any parents or children. + + The new clone. + + + + Gets the current animationChain - retrieved through the CurrentChainIndex property. + + + + + The time returned by the TimeManager when the Sprite was created. + + + This value is automatically set when the Sprite + is added through the SpriteManager. If a Sprite is created manually (either as a + Sprite or a class inheriting from the Sprite class) this value should be set manually + if it is to be used later. + + + + + Controls the visibility of the Sprite + + + This variable controls the visiblity of the Sprite. Sprites are visible + by default. Setting Visible to false will make the sprite invisible, but + the Sprite will continue to behave regularly; custom behavior, movement, attachment, + and animation are still executed, and collision is possible. + + + + + Controls the Sprite's transparency. + + + Fade controls a Sprite's transparency. A completely opaque Sprite has an + Alpha of 1 while a completely transparent object has an Alpha of 0. + + Setting the AlphaRate of a completely opaque Sprite to -1 will + make the sprite disappear in one second. Invisible Sprites continue + to remain in memory and are managed by the SpriteManager. The Alpha variable + will automatically regulate itself if the value is set to something outside of the + 0 - 1 range. + + + + + Sets the rate at which the Alpha property changes. This is in units per second. A fully opaque + Sprite (Alpha = 1) will disappear in 1 second if its AlphaRate is set to -1. + + + The AlphaRate changes Alpha as follows: + + Alpha += AlphaRate * TimeManager.SecondDifference; + + This is automatically applied if the Sprite is managed by the SpriteManager(usually the case). + + + + + Whether to flip the Sprite's texture on the y Axis (left and right switch). + + + This kind of texture rotation can be accomplished by simply rotating + a Sprite on its yAxis; however, there are times when this + is inconvenient or impossible due to attachment relationships. There + is no efficiency consequence for using either method. If a Sprite + is animated, this value will be overwritten by the AnimationChain being used. + + + + + Whether to flip the Sprite's texture on the x Axis (top and bottom switch). + + + This kind of texture rotation can be accomplished by simply rotating a + Sprite on its xAxis; however, there are times when this + is inconvenient or impossible due to attachment relationships. + There is no efficiency consequence for using either method. If a Sprite + is animated, this value will be overwritten by the AnimationChain being used. + + + + + These can be used to change Sprite appearance + on individual vertices. + + + The index begins counting at the top left (index 0) + and increases moving clockwise. + + + + + Delegate for methods which can be assigned to the Sprite for every-frame + custom logic or when a Sprite is removed. + + + + + + The Sprite on which the logic should execute. + + + + Returns a one-way SpriteList containing all Sprites in this SpriteList which reference the texture argument. + + The texture to match against. + SpriteList containing Sprites with matching textures. + + + + Sorts a sub-array of the SpriteArray by their Texture. + + Index of the first Sprite, inclusive. + Index of the last Sprite, exclusive. + + + + Contains logic for updating objects. This is used to separate + updates into different threads + + + + + Static manager class which handles the management of Sprites, SpriteFrames, Cameras, and + other PositionedObjects. + + + The SpriteManager is commonly used in FlatRedBall examples to create Sprites. Perhaps the most + common line of code is: + + Sprite sprite = SpriteManager.AddSprite("redball.bmp"); + + Originally the SpriteManager was the only manager class. For this reason it handled + some of the functionality which may have normally belonged to classes unrelated to Sprites. + For example, Cameras are added and managed through the SpriteManager although they are a general + rendering class which might belong in the Renderer or FlatRedBallServices class. + + + + + SpriteList filled with valid Sprites at the start of execution. + + + Particle Sprites are defined as Sprites which have a short lifespan and + are created frequently. Common uses are smoke, bullets, and explosions. + + + + + This function is inteded for internal use only during rendering. + + + + + + + Creates a new SpriteManager. Also creates a new FileManager, TextureManager, and Renderer. + + + + + + Returns the first object which is added twice to the SpriteManager, or null if none are found. + + The first duplicate object or null. + + + + Returns whether the SpriteManager is holding a reference to the argment sprite. + + The sprite to check references for. + Whether the argument Sprite is referenced by the SpriteManager. + + + + Removes the argument DrawableBatch from the internal list and calls its Destroy method. + + The DrawableBatch to remove. + + + + Removes the Sprite from the SpriteManager but preserves emitters, attachment and children references. + + + Although the removed Sprite will preserve its parent and children, the parent will + no longer see this Sprite as its child, and the children will no longer see this + Sprite as their parent. The preservation of relationships is not a functional one. Usually + this method is used to keep relationships alive on a removed Sprite for reattachment at + a later time. + + The Sprite to remove. + + + + Finds all contained Sprites which reference the argument oldTexture and replaces + the reference with newTexture. + + + This method will preserve all sizes of Sprites even if they use a non-zero PixelSize. + + The old Texture2D to replace. + The new Texture2D to use as a replacement. + + + + Updates the dependencies of all contained automatically updated Sprites, Cameras, and SpriteFrames. + + + + + A read-only collection of Sprites which are automatically managed + every frame. + + + Sprites in this list have all of their behavioral properties + applied automatically. Examples of these properties include + Velocity, Acceleration, rotational velocity, and color rates. + This manage requires overhead which can become a significant portion + of frame time, so moving Sprites to be manually updated can improve performance. + + Most methods which create Sprites will place the created Sprite in this list. + + + + + + + + Gets the default Camera. + + + If your application is only using one Camera, then this Camera can be + used for all logic. This Camera is automatically created by the engine, + so single-camera applications do not need to instantiate their own Camera. + + + + + The List of all Cameras used by FlatRedBall. Any Camera in this List is managed and + renders according to its settings. + + + Adding a Camera to this list will result in the Camera being managed by the engine and + rendered. There is no AddCamera method - simply adding the Camera to this list is sufficient + for adding it to the engine. + + + + + A read-only collection of IDrawableBatches. + + + + + + + + + When not set to 0 and the number of particles on screen exceed what is available, then + the MaxParticleCount will be incremented by this amount. Otherwise, it will throw + an out of particles exception. + + + + + Read-only collection of SpriteFrames managed by the SpriteManager. + + + + + Gets and sets the sorting type used on Sprites, Text objects, and DrawableBatches + in the world (not on layers). + + + + + Represents the unit of time measurement. This can be used in files that store timing information. + + + + + Allows the game component to perform any initialization it needs to before starting + to run. This is where it can query for any required services and load content. + + + + + Begins Sum Timing + + + + + StartSumTiming(); + + foreach(Sprite sprite in someSpriteArray) + { + SumTimeRefresh(); + PerformSomeFunction(sprite); + SumTimeSection("PerformSomeFunction time:"); + + + SumTimeRefresh(); + PerformSomeOtherFunction(sprite); + SumTimeSection("PerformSomeOtherFunction time:); + + } + + + + + + + Stores an unnamed timed section. + + + A timed section is the amount of time (in seconds) since the last time either Update + or TimeSection has been called. The sections are reset every time Update is called. + The sections can be retrieved through the GetTimedSections method. + + + + + + Stores an named timed section. + + + A timed section is the amount of time (in seconds) since the last time either Update + or TimeSection has been called. The sections are reset every time Update is called. + The sections can be retrieved through the GetTimedSections method. + + + The label for the timed section. + + + + Performs every-frame logic to update timing values such as CurrentTime and SecondDifference. If this method is not called, CurrentTime will not advance. + + The GameTime value provided by the XNA Game class. + + + + The number of seconds (usually a fraction of a second) since + the last frame. This value can be used for time-based movement. + + + + + A multiplier for how fast time runs. This is 1 by default. Setting + this value to 2 will make everything run twice as fast. + + + + Places the current screen onto the back stack, and moves to the next one. + + + The current screen will not be put onto the backstack. + + + Support class used to add navigation features to the Screen API. + The behavior of this is that if an item exists on the stack already, the backstack will + "go back" until it reaches that item. This behavior helps avoid circular dependencies and promotes + tree/leaf navigation. + + + Moves to the previous item on the stack. + default(T) if the stack is empty, otherwise, returns the previous item on the stack. + + + Call this method in scenarios where you want to skip the current item when navigating back from the next + value. + + + If the value exists in the stack, will pop items on the stack until it returns to that value. + Otherwise will add on top of the stack. BackStackBehavior defaults to Move. + + + If the value exists in the stack, will pop items on the stack until it returns to that value. + Otherwise will add on top of the stack. BackStackBehavior defaults to Move. + + + avoid setting this property, setter is only public to support serialization when we tombstone. + + + This property is here to support serialization when we tombstone. + + + + Static class containing a collection of common methods used as CustomBehaviors. + + + + + Removes the argument Sprite when its Alpha equals 0. + + The Sprite to remove. + + + + Removes the argument Sprite when it is outside of the default Camera's view (SpriteManager.Camera). + + The Sprite to remove. + + + + Removes teh argument Sprite after it cycles its AnimationChain. + + The Sprite to remove. + + + + Removes the argument Sprite if it is opaque. + + + This method is commonly used with Sprites which have a + positive AlphaRate and which should be removed when completely visible. + + Sprite to remove.\ + + + + Class providing methods which can help during debugging. + + + + + Represents the order that variables appear in Glue. + + + + + Contains information for simulating an expansion or contraction of a Sprite. + + + This struct is used by a number of FlatRedball classes such as SpriteGrid and MathFunctions. + + + + + Get index of a char + + + + + + + + Get index of a char starting from a given index + + + + + + + + + Get index of a string + + + + + + + + Get index of a string from a given index + + + + + + + + + Get index of a string with case option + + + + + + + + + Get index of a string from a given index with case option + + + + + + + + + + Helper class for working with 'extended' enums using attributes. + + + + + Creates a new instance. + + Enum type. + + + + Gets the string value associated with the given enum value. + + Name of the enum value. + String Value + + + + Gets the string values associated with the enum. + + String value array + + + + Gets the values as a 'bindable' list datasource. + + IList for data binding + + + + Return the existence of the given string value within the enum. + + String value. + Existence of the string value + + + + Return the existence of the given string value within the enum. + + String value. + Denotes whether to conduct a case-insensitive match on the supplied string value + Existence of the string value + + + + Gets a string value for a particular enum value. + + Value. + String Value associated via a attribute, or null if not found. + + + + Parses the supplied enum and string value to find an associated enum value (case sensitive). + + Type. + String value. + Enum value associated with the string value, or null if not found. + + + + Parses the supplied enum and string value to find an associated enum value. + + Type. + String value. + Denotes whether to conduct a case-insensitive match on the supplied string value + Enum value associated with the string value, or null if not found. + + + + Return the existence of the given string value within the enum. + + String value. + Type of enum + Existence of the string value + + + + Return the existence of the given string value within the enum. + + String value. + Type of enum + Denotes whether to conduct a case-insensitive match on the supplied string value + Existence of the string value + + + + Gets the underlying enum type for this instance. + + + + + + Simple attribute class for storing String Values + + + + + Creates a new instance. + + Value. + + + + Gets the value. + + + + + + A class containing common string maniuplation methods. + + + + + Returns the number of times that the argument whatToFind is found in the calling string. + + The string to search within. + The string to search for. + The number of instances found + + + + Returns the number of non-whitespace characters in the argument stringInQuestion. + + + This method is used internally by the TextManager to determine the number of vertices needed to + draw a Text object. + + The string to have its non-witespace counted. + The number of non-whitespace characters counted. + + + + Returns a string of the float with the argument decimalsAfterPoint digits of resolution after the point. + + The float to convert. + The number of decimals after the point. For example, 3.14159 becomes "3.14" if the + decimalsAfterPoint is 2. This method will not append extra decimals to reach the argument decimalsAfterPoint. + The string representation of the argument float. + + + + Returns the character that can be found after a particular sequence of characters. + + + This will return the first character following a particular sequence of characters. For example, + GetCharAfter("bcd", "abcdef") would return 'e'. + + The string to search for. + The string to search in. + Returns the character found or the null character '\0' if the string is not found. + + + + Returns the float that can be found after a particular sequence of characters. + + + This will return the float following a particular sequence of characters. For example, + GetCharAfter("height = 6; width = 3; depth = 7;", "width = ") would return 3.0f. + + The string to search for. + The string to search in. + Returns the float value found or float.NaN if the string is not found. + + + + Returns the first integer found after the argument stringToSearchFor in whereToSearch. + + + This method is used to help simplify parsing of text files and data strings. + If stringToSearchFor is "Y:" and whereToSearch is "X: 30, Y:32", then the value + of 32 will be returned. + + The string pattern to search for. + The string that will be searched. + The integer value found after the argument stringToSearchFor. + + + + Returns the first integer found after the argument stringToSearchFor. The search begins + at the argument startIndex. + + The string pattern to search for. + The string that will be searched. + The index to begin searching at. This method + will ignore any instances of stringToSearchFor which begin at an index smaller + than the argument startIndex. + + + + + Returns the number of lines in a given string. Newlines '\n' increase the + line count. + + The string that will have its lines counted. + The number of lines in the argument. "Hello" will return a value of 1, "Hello\nthere" will return a value of 2. + + + + Returns the number found at the end of the argument stringToGetNumberFrom or throws an + ArgumentException if no number is found. + + + A stringToGetNumberFrom of "sprite41" will result in the value of 41 returned. A + stringToGetNumberFrom of "sprite" will result in an ArgumentException being thrown. + + Throws ArgumentException if no number is found at the end of the argument string. + The number found at the end. + The integer value found at the end of the stringToGetNumberFrom. + + + + Increments the number at the end of a string or adds a number if none exists. + + + This method begins looking at the end of a string for numbers and moves towards the beginning of the string + until it encounters a character which is not a numerical digit or the beginning of the string. "Sprite123" would return + "Sprite124", and "MyString" would return "MyString1". + + The string to "increment". + Returns a string with the number at the end incremented, or with a number added on the end if none existed before. + + + + Inserts spaces before every capital letter in a camel-case + string. Ignores the first letter. + + + For example "HelloThereIAmCamelCase" becomes + "Hello There I Am Camel Case". + + The string in which to insert spaces. + The string with spaces inserted. + + + + Renames the argument INameable to prevent duplicate names. This method is extremely inefficent for large lists. + + The type of INameable contained int he list. + The INameable to rename if necessary. + The list containing the INameables to compare against. + + + + Makes an INameable's name unique given a list of existing INameables. + + The type of nameable. + The type of IList, where the type is an INameable + The instance to modify if necessary. + The list of INameables + + + + Removes the number found at the end of the argument originalString and returns the resulting + string, or returns the original string if no number is found. + + The string that will have the number at its end removed. + The string after the number has been removed. + + + + Removes all whitespace found in the argument stringToRemoveWhitespaceFrom. + + The string that will have its whitespace removed. + The string resulting from removing whitespace from the argument string. + +
+
diff --git a/FRBDK/GlueView2/Dependencies/FlatRedBall.dll b/FRBDK/GlueView2/Dependencies/FlatRedBall.dll new file mode 100644 index 000000000..fb3d9ede0 Binary files /dev/null and b/FRBDK/GlueView2/Dependencies/FlatRedBall.dll differ diff --git a/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Game.dll b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Game.dll new file mode 100644 index 000000000..9ba4aa23b Binary files /dev/null and b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Game.dll differ diff --git a/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Graphics.dll b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Graphics.dll new file mode 100644 index 000000000..1bf4852d6 Binary files /dev/null and b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.Graphics.dll differ diff --git a/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.dll b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.dll new file mode 100644 index 000000000..a0caf6af2 Binary files /dev/null and b/FRBDK/GlueView2/Dependencies/Microsoft.Xna.Framework.dll differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/App.config b/FRBDK/GlueView2/FlatRedBallWpf/App.config new file mode 100644 index 000000000..2d63d2f32 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/App.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/FRBDK/GlueView2/FlatRedBallWpf/App.xaml b/FRBDK/GlueView2/FlatRedBallWpf/App.xaml new file mode 100644 index 000000000..e4a3a2fa7 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/FRBDK/GlueView2/FlatRedBallWpf/App.xaml.cs b/FRBDK/GlueView2/FlatRedBallWpf/App.xaml.cs new file mode 100644 index 000000000..94f04f855 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/App.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace FlatRedBallWpf +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/AppState/GlueViewState.cs b/FRBDK/GlueView2/FlatRedBallWpf/AppState/GlueViewState.cs new file mode 100644 index 000000000..c0f937fa0 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/AppState/GlueViewState.cs @@ -0,0 +1,27 @@ +using FlatRedBall.Glue.SaveClasses; +using FlatRedBall.Screens; +using GlueView2.Patterns; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.AppState +{ + public class GlueViewState : Singleton + { + public GlueProjectSave GlueProject + { + get; set; + } + + + public Screen CurrentScreenRuntime + { + get; + set; + } + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Commands/CurrentScreenManager.cs b/FRBDK/GlueView2/FlatRedBallWpf/Commands/CurrentScreenManager.cs new file mode 100644 index 000000000..130394bc5 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Commands/CurrentScreenManager.cs @@ -0,0 +1,51 @@ +using FlatRedBall.Screens; +using GlueView2.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.Commands +{ + public class CurrentScreenManager + { + public Assembly CurrentAssembly + { + get; + private set; + } + + FilePath projectRootFolder; + + public Screen CurrentScreen + { + get; + private set; + } + + + + public void HandleLoadedAssembly (Assembly newAssembly, FilePath root) + { + // todo - unload assembly? + + projectRootFolder = root; + CurrentAssembly = newAssembly; + } + + public void ShowScreen(string screenName) + { + if(CurrentScreen != null) + { + CurrentScreen.Destroy(); + } + + CurrentScreen = (Screen)CurrentAssembly.CreateObject(screenName); + + FlatRedBall.IO.FileManager.RelativeDirectory = projectRootFolder.FullPath; + CurrentScreen.Initialize(true); + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Commands/GlueViewCommands.cs b/FRBDK/GlueView2/FlatRedBallWpf/Commands/GlueViewCommands.cs new file mode 100644 index 000000000..b7200f2e8 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Commands/GlueViewCommands.cs @@ -0,0 +1,70 @@ +using FlatRedBall.Glue.SaveClasses; +using FlatRedBall.IO; +using FlatRedBall.Screens; +using FlatRedBallWpf.ScriptLoading; +using GlueView2.AppState; +using GlueView2.IO; +using GlueView2.Patterns; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.Commands +{ + public class GlueViewCommands : Singleton + { + CurrentScreenManager currentScreenManager; + public CurrentScreenManager CurrentScreenManager + { + get + { + if(currentScreenManager == null) + { + currentScreenManager = new CurrentScreenManager(); + } + + return currentScreenManager; + } + } + + ScriptLoadingLogic scriptLoadingLogic; + public ScriptLoadingLogic ScriptLoadingLogic + { + get + { + if(scriptLoadingLogic == null) + { + scriptLoadingLogic = new ScriptLoadingLogic(); + } + return scriptLoadingLogic; + } + } + + public GlueProjectSave GlueProject + { + get; private set; + } + + public void LoadProject(FilePath filePath) + { + GlueViewState.Self.GlueProject = FileManager.XmlDeserialize(filePath.FullPath); + + + var directory = filePath.GetDirectoryContainingThis(); + + var loadedGameAssembly = ScriptLoadingLogic.LoadProjectCode(directory.FullPath); + + CurrentScreenManager.HandleLoadedAssembly(loadedGameAssembly, directory); + } + + public void ShowScreen(string screenName) + { + + CurrentScreenManager.ShowScreen(screenName); + + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Content/redball.BMP b/FRBDK/GlueView2/FlatRedBallWpf/Content/redball.BMP new file mode 100644 index 000000000..6067373f5 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Content/redball.BMP differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml new file mode 100644 index 000000000..e0c51a3ee --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml.cs b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml.cs new file mode 100644 index 000000000..793847a9b --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallControl.xaml.cs @@ -0,0 +1,17 @@ +using System; + +namespace FlatRedBallWpf +{ + public partial class FlatRedBallControl + { + public FlatRedBallControl() + { + InitializeComponent(); + } + + public IntPtr Handle + { + get { return GamePanel.Handle; } + } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallGameBase.cs b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallGameBase.cs new file mode 100644 index 000000000..4fb3fc9d9 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallGameBase.cs @@ -0,0 +1,165 @@ +using System; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Threading; +using FlatRedBall; +using FlatRedBall.Graphics; +using FlatRedBall.Input; +using FlatRedBall.Math; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FlatRedBallWpf +{ + public class FlatRedBallGameBase : Game + { + private FlatRedBallControl _frbControl; + + #region Constructors + + public FlatRedBallGameBase(FlatRedBallControl frbControl) + { + this._frbControl = frbControl; + + // Get the starting size of the control and start listening + // to its Resize events. + _windowHandle = frbControl.Handle; + RenderWidth = (Int32) frbControl.RenderSize.Width; + RenderHeight = (Int32) frbControl.RenderSize.Height; + frbControl.SizeChanged += XnaControlOnSizeChanged; + + // Create the graphics device manager and set the delegate for initializing the graphics device + Graphics = new GraphicsDeviceManager(this); + Graphics.PreferMultiSampling = true; + + Graphics.SynchronizeWithVerticalRetrace = true; + Graphics.PreparingDeviceSettings += PreparingDeviceSettings; + + StartInitialization(); + + Content.RootDirectory = "Content"; + } + + #endregion + + #region Methods + + protected override void Initialize() + { + // Create a timer that will simulate the gameloop + _timer = new DispatcherTimer(); + _timer.Interval = TimeSpan.FromSeconds(1/60); + _timer.Tick += TimerTick; + _timer.Start(); + + // Create the graphics options and initialize FRB with them + var graphicsOptions = new GraphicsOptions(this, Graphics); + graphicsOptions.SuspendDeviceReset(); + graphicsOptions.ResolutionWidth = RenderWidth; + graphicsOptions.ResolutionHeight = RenderHeight; + graphicsOptions.ResumeDeviceReset(); + FlatRedBallServices.InitializeFlatRedBall(this, Graphics, graphicsOptions); + FlatRedBall.Screens.ScreenManager.Start(typeof(FlatRedBallWpf.Screens.MainScreen)); + + if(false) + { + // Don't run this because we want the game's camera setup to run + // Put if(false) so that it doesn't get re-generated + CameraSetup.SetupCamera(SpriteManager.Camera, this.Graphics); + } + GlobalContent.Initialize(); + Mouse.ModifyMouseState += HandleModifyMouseState; + + FlatRedBall.Gui.GuiManager.Cursor.CustomIsActive = HandleCustomIsActive; + + base.Initialize(); + } + + private void XnaControlOnSizeChanged(object sender, SizeChangedEventArgs e) + { + // Get the new size + var newWidth = (Int32) e.NewSize.Width; + var newHeight = (Int32) e.NewSize.Height; + + // Update FRB + FlatRedBallServices.GraphicsOptions.SetResolution(newWidth, newHeight); + Rectangle displayRectangle = new Rectangle(0, 0, newWidth, newHeight); + SpriteManager.Camera.DestinationRectangle = displayRectangle; + + + double unitPerPixel = SpriteManager.Camera.OrthogonalHeight / + SpriteManager.Cameras[0].DestinationRectangle.Height; + + SpriteManager.Camera.OrthogonalHeight = (float)(displayRectangle.Height * unitPerPixel); + SpriteManager.Camera.OrthogonalWidth = (float)(displayRectangle.Width * unitPerPixel); + + + SpriteManager.Camera.UsePixelCoordinates(); + } + + private void StartInitialization() + { + Initialize(); + } + + private void PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e) + { + PresentationParameters presentationParams = e.GraphicsDeviceInformation.PresentationParameters; + + presentationParams.BackBufferWidth = RenderWidth; + presentationParams.BackBufferHeight = RenderHeight; + presentationParams.DeviceWindowHandle = _windowHandle; + presentationParams.RenderTargetUsage = RenderTargetUsage.PreserveContents; + } + + private void TimerTick(object sender, EventArgs e) + { + Tick(); + } + + protected override void EndDraw() + { + base.EndDraw(); + + GraphicsDevice.Present(); + } + + private void HandleModifyMouseState(ref Microsoft.Xna.Framework.Input.MouseState mouseState) + { + System.Drawing.Point point = Control.MousePosition; + + var screen = _frbControl.PointFromScreen(new System.Windows.Point(point.X, point.Y)); + var newMouseState = new Microsoft.Xna.Framework.Input.MouseState( + MathFunctions.RoundToInt(screen.X), + MathFunctions.RoundToInt(screen.Y), + mouseState.ScrollWheelValue, + mouseState.LeftButton, + mouseState.MiddleButton, + mouseState.RightButton, + mouseState.XButton1, + mouseState.XButton2); + mouseState = newMouseState; + } + + + private bool HandleCustomIsActive() + { + return System.Windows.Application.Current.MainWindow.IsActive; + } + + + #endregion + + #region Fields + + protected readonly GraphicsDeviceManager Graphics; + protected readonly Int32 RenderHeight; + protected readonly Int32 RenderWidth; + private readonly IntPtr _windowHandle; + protected GraphicsDeviceManager GraphicsDeviceManager; + protected Boolean IsRenderingPaused; + private DispatcherTimer _timer; + + #endregion + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallWpf.glux b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallWpf.glux new file mode 100644 index 000000000..fed6c3965 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/FlatRedBallWpf.glux @@ -0,0 +1,81 @@ + + + true + false + false + 800 + 600 + false + 800 + 600 + + + + GLUE + + GLUE + Screens\MainScreen + + + + + + false + false + + + Screens\MainScreen + + + false + false + + + + + + + + bool + HasCollision + + + string + EntityToCreate + + + string + Name + + + System.Collections.Generic.List<FlatRedBall.Content.AnimationChain.AnimationFrameSaveBase> + EmbeddedAnimation + + + TileMapInfo + true + + + + true + ../../ + + + + + true + true + 800 + 600 + false + 16 + 9 + true + false + false + false + 100 + StretchVisibleArea + Height + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Game1.cs b/FRBDK/GlueView2/FlatRedBallWpf/Game1.cs new file mode 100644 index 000000000..302b92951 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Game1.cs @@ -0,0 +1,56 @@ +using FlatRedBall; +using FlatRedBall.Gui; +using FlatRedBall.Input; +using FlatRedBall.Math; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FlatRedBallWpf +{ + public class Game1 : FlatRedBallGameBase + { + + + public Game1(FlatRedBallControl frbControl) + : base(frbControl) + { + } + + /// + /// This method is where you'll set up your camera, create sprites, etc. + /// + protected override void Initialize() + { + // This needs to be called first. Do not remove it. + base.Initialize(); + } + + protected override void Update(GameTime gameTime) + { + // Update FRB. + FlatRedBallServices.Update(gameTime); + + FlatRedBall.Screens.ScreenManager.Activity(); + + // This needs to be called. Do not remove it. + base.Update(gameTime); + + + + } + + protected override void Draw(GameTime gameTime) + { + if (IsRenderingPaused) + { + return; + } + + // Draw FRB. + FlatRedBallServices.Draw(); + + // This needs to be called. Do not remove it. + base.Draw(gameTime); + } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/GlueSettings/BuildToolAssociation.xml b/FRBDK/GlueView2/FlatRedBallWpf/GlueSettings/BuildToolAssociation.xml new file mode 100644 index 000000000..463e0a139 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/GlueSettings/BuildToolAssociation.xml @@ -0,0 +1,14 @@ + + + + + %Glue%Libraries\BMFont\bmfont.exe + false + bmfc + fnt + true + -c + -o + + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/GlueView2.csproj b/FRBDK/GlueView2/FlatRedBallWpf/GlueView2.csproj new file mode 100644 index 000000000..5ae47f0a4 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/GlueView2.csproj @@ -0,0 +1,325 @@ + + + + + Debug + AnyCPU + {B44EEB5C-9155-437C-9AF2-DD6688192185} + WinExe + Properties + GlueView2 + GlueView2 + v4.7.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + + x86 + true + full + false + bin\Debug\ + TRACE;DEBUG;XNA4,WINDOWS + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;XNA4,WINDOWS + prompt + 4 + + + + ..\packages\CS-Script.bin.3.28.7\lib\net46\CSScriptLibrary.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + + False + ..\Dependencies\Microsoft.Xna.Framework.dll + + + False + ..\Dependencies\Microsoft.Xna.Framework.Game.dll + + + False + ..\Dependencies\Microsoft.Xna.Framework.Graphics.dll + + + ..\packages\CS-Script.bin.3.28.7\lib\net46\Mono.CSharp.dll + + + + ..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + True + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + True + + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + True + + + ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + True + + + + ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + + + ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + True + + + ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + True + + + ..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + True + + + + + + 4.0 + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + True + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + True + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + True + + + + + + + + + MSBuild:Compile + Designer + + + + + + + + FlatRedBallControl.xaml + + + + + + MainScreen.cs + + + + + + + + + + + + + + + + + + + + + + + + ReducedTileMapInfo.cs + + + + + + + + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + + MainWindow.xaml + Code + + + MSBuild:Compile + Designer + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + PreserveNewest + + + + + {e1cb7d7b-e2ec-4deb-92e2-6ef0b76f40f0} + FlatRedBallXna4 + + + {f0bdee46-0374-4278-b1c3-f91a76fe3a8d} + StateInterpolation + + + {e1d670b6-ad42-4b84-aff8-d568097be03d} + EditorObjectsXna4 + + + {545ff183-4d9d-4121-9a04-1d354b0b0dba} + GlueSaveClasses + + + + + + + + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/IO/FilePath.cs b/FRBDK/GlueView2/FlatRedBallWpf/IO/FilePath.cs new file mode 100644 index 000000000..3662d2762 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/IO/FilePath.cs @@ -0,0 +1,208 @@ +using FlatRedBall.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.IO +{ + public class FilePath + { + #region Fields + + string original; + + #endregion + + #region Operators + + public static bool operator ==(FilePath f1, FilePath f2) + { + return f1?.Standardized == f2?.Standardized; + } + + public static bool operator !=(FilePath f1, FilePath f2) + { + return (f1 == f2) == false; + } + + public static implicit operator FilePath(string s) + { + // Code to convert the book into an XML structure + return new FilePath(s); + } + + #endregion + + #region Properties + + public string Extension + { + get + { + return FileManager.GetExtension(original); + } + } + + public string StandardizedNoPathNoExtension + { + get + { + return FileManager.RemovePath(FileManager.RemoveExtension(Standardized)); + } + } + + public string FullPath + { + get + { + FileManager.PreserveCase = true; + if (string.IsNullOrEmpty(original)) + { + return FileManager.RemoveDotDotSlash(StandardizeInternal("")); + } + else + { + return FileManager.RemoveDotDotSlash(StandardizeInternal(original)); + } + } + } + + public string Standardized + { + get + { + if (string.IsNullOrEmpty(original)) + { + return FileManager.RemoveDotDotSlash(StandardizeInternal("")).ToLowerInvariant(); + } + else + { + return FileManager.RemoveDotDotSlash(StandardizeInternal(original)).ToLowerInvariant(); + } + } + } + + #endregion + + /// + /// Creates a file path for the original. If this is an absolute file, then it is stored as such and the Standaridzed property will return the same absolute file. If it is lower-case, then Standardized prepends the current relative directory. + /// + /// + public FilePath(string path) + { + original = path; + } + + public override bool Equals(object obj) + { + if (obj is FilePath) + { + var path = obj as FilePath; + return path != null && + Standardized == path.Standardized; + } + else if (obj is string) + { + var path = new FilePath(obj as string); + return path != null && + Standardized == path.Standardized; + } + else + { + return false; + } + } + + public FilePath GetDirectoryContainingThis() + { + return FlatRedBall.IO.FileManager.GetDirectory(this.FullPath); + } + + public override int GetHashCode() + { + var hashCode = 354063820; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Standardized); + return hashCode; + } + + public bool Exists() + { + return System.IO.File.Exists(this.Standardized); + } + + public bool IsRootOf(FilePath otherFilePath) + { + return otherFilePath.Standardized.StartsWith(this.Standardized); + } + + public FilePath RemoveExtension() + { + var fileString = FileManager.RemoveExtension(original); + + return fileString; + } + + public override string ToString() + { + return FullPath; + } + + static void ReplaceSlashes(ref string stringToReplace) + { + bool isNetwork = false; + if (stringToReplace.StartsWith("\\\\")) + { + stringToReplace = stringToReplace.Substring(2); + isNetwork = true; + } + + stringToReplace = stringToReplace.Replace("\\", "/"); + + if (isNetwork) + { + stringToReplace = "\\\\" + stringToReplace; + } + } + + private string StandardizeInternal(string fileNameToFix) + { + if (fileNameToFix == null) + return null; + + bool isNetwork = fileNameToFix.StartsWith("\\\\"); + + ReplaceSlashes(ref fileNameToFix); + + if (!isNetwork) + { + // Not sure what this is all about, but everything should be standardized: + //#if SILVERLIGHT + // if (IsRelative(fileNameToFix) && mRelativeDirectory.Length > 1) + // fileNameToFix = mRelativeDirectory + fileNameToFix; + + //#else + + if (FileManager.IsRelative(fileNameToFix)) + { + fileNameToFix = (FileManager.RelativeDirectory + fileNameToFix); + ReplaceSlashes(ref fileNameToFix); + } + + //#endif + } + + fileNameToFix = FileManager.RemoveDotDotSlash(fileNameToFix); + + if (fileNameToFix.StartsWith("..")) + { + throw new InvalidOperationException("Tried to remove all ../ but ended up with this: " + fileNameToFix); + } + // It's possible that there will be double forward slashes. + fileNameToFix = fileNameToFix.Replace("//", "/"); + + return fileNameToFix; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/FlatRedBall.dll b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/FlatRedBall.dll new file mode 100644 index 000000000..b1b9a245a Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/FlatRedBall.dll differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.dll b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.dll new file mode 100644 index 000000000..c9358688b Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.dll differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.pdb b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.pdb new file mode 100644 index 000000000..5db778a48 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TMXGlueLib.pdb differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.exe b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.exe new file mode 100644 index 000000000..27cfd98bd Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.exe differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.pdb b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.pdb new file mode 100644 index 000000000..6b5d9a0bf Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToCSV.pdb differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.exe b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.exe new file mode 100644 index 000000000..6c4ed2471 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.exe differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.pdb b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.pdb new file mode 100644 index 000000000..1251172dc Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToNntx.pdb differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.exe b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.exe new file mode 100644 index 000000000..e320f5644 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.exe differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.pdb b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.pdb new file mode 100644 index 000000000..aa9d89330 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToScnx.pdb differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.exe b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.exe new file mode 100644 index 000000000..64aba89ba Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.exe differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.pdb b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.pdb new file mode 100644 index 000000000..d80f04e05 Binary files /dev/null and b/FRBDK/GlueView2/FlatRedBallWpf/Libraries/Tmx/TmxToShcx.pdb differ diff --git a/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml b/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml new file mode 100644 index 000000000..56d09a854 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml.cs b/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml.cs new file mode 100644 index 000000000..58575b42a --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/MainWindow.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows; + +namespace FlatRedBallWpf +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private Game1 _mainGame; + + public MainWindow() + { + InitializeComponent(); + + Loaded += OnLoaded; + } + + private void OnLoaded(Object sender, RoutedEventArgs routedEventArgs) + { + _mainGame = new Game1(flatRedBallControl); + } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Patterns/Singleton.cs b/FRBDK/GlueView2/FlatRedBallWpf/Patterns/Singleton.cs new file mode 100644 index 000000000..5973639a5 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Patterns/Singleton.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.Patterns +{ + public class Singleton where T : new() + { + static T mSelf; + + public static T Self + { + get + { + if (mSelf == null) + { + mSelf = new T(); + } + return mSelf; + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Properties/AssemblyInfo.cs b/FRBDK/GlueView2/FlatRedBallWpf/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8df1e2a43 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FlatRedBallWpf")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FlatRedBallWpf")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.Designer.cs b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.Designer.cs new file mode 100644 index 000000000..780e8df2f --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GlueView2.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GlueView2.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.resx b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.Designer.cs b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.Designer.cs new file mode 100644 index 000000000..f134847e9 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GlueView2.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.settings b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Screens/MainScreen.cs b/FRBDK/GlueView2/FlatRedBallWpf/Screens/MainScreen.cs new file mode 100644 index 000000000..b6a8d4b56 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Screens/MainScreen.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +using FlatRedBall; +using FlatRedBall.Input; +using FlatRedBall.Instructions; +using FlatRedBall.AI.Pathfinding; +using FlatRedBall.Graphics.Animation; +using FlatRedBall.Graphics.Particle; +using FlatRedBall.Math.Geometry; +using FlatRedBall.Localization; +using Microsoft.Xna.Framework.Graphics; +using FlatRedBallWpf.ScriptLoading; +using FlatRedBall.Screens; +using GlueView2.Commands; + +namespace FlatRedBallWpf.Screens +{ + public partial class MainScreen + { + + void CustomInitialize() + { + GlueViewCommands.Self.LoadProject( + @"C:\Users\Victor\Documents\FlatRedBallProjects\GViewTargetProject\GViewTargetProject\GViewTargetProject.glux"); + } + + void CustomActivity(bool firstTimeCalled) + { + if (InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.Space)) + { + GlueViewCommands.Self.ShowScreen("GViewTargetProject.Screens.GameScreen"); + } + } + + void CustomDestroy() + { + + + } + + static void CustomLoadStaticContent(string contentManagerName) + { + + + } + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/ScriptLoading/ScriptLoadingLogic.cs b/FRBDK/GlueView2/FlatRedBallWpf/ScriptLoading/ScriptLoadingLogic.cs new file mode 100644 index 000000000..1d8a019ff --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/ScriptLoading/ScriptLoadingLogic.cs @@ -0,0 +1,124 @@ +using CSScriptLibrary; +using FlatRedBall.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBallWpf.ScriptLoading +{ + public class ScriptLoadingLogic + { + public Assembly LoadProjectCode(string projectDirectoryFullPath) + { + + List filesToCompile = new List(); + + // preload this: + var tweenerManager = typeof(global::StateInterpolationPlugin.TweenerManager); + + AddCameraSetup(projectDirectoryFullPath, filesToCompile); + + AddScreensAndEntities(projectDirectoryFullPath, filesToCompile); + + var assembly = CSScript.LoadFiles(filesToCompile.ToArray()); + return assembly; + } + + private void AddScreensAndEntities(string projectDirectoryFullPath, List filesToCompile) + { + string projectName = FileManager.RemovePath(projectDirectoryFullPath); + // remove trailing slash + projectName = projectName.Substring(0, projectName.Length - 1); + + + var screenDirectory = projectDirectoryFullPath + @"Screens\"; + var allScreenFiles = System.IO.Directory.GetFiles(screenDirectory) + .Where(item => item.Contains(".Generated.")) + .ToArray(); + filesToCompile.AddRange(allScreenFiles); + + var entitiesDirectory = projectDirectoryFullPath + @"Entities\"; + var allEntityFiles = System.IO.Directory.GetFiles(entitiesDirectory) + .Where(item => item.Contains(".Generated.")) + .ToArray(); + + filesToCompile.AddRange(allEntityFiles); + + var targetFile = AddEmptyCustomCodeFor(allScreenFiles, allEntityFiles, projectName); + filesToCompile.Add(targetFile); + } + + private string AddEmptyCustomCodeFor(string[] allScreenFiles, string[] allEntityFiles, string projectName) + { + var stringBuilder = new StringBuilder(); + + foreach (var screenFile in allScreenFiles) + { + var fileName = + FileManager.RemovePath(FileManager.RemoveExtension(FileManager.RemoveExtension(screenFile))); + + string emptyScreenContents = CreateEmptyPartialForScreen(projectName, fileName); + + stringBuilder.AppendLine(emptyScreenContents); + } + + foreach (var entityFile in allEntityFiles) + { + var fileName = + FileManager.RemovePath(FileManager.RemoveExtension(FileManager.RemoveExtension(entityFile))); + + string emptyEntityContents = CreateEmptyPartialForEntity(projectName, fileName); + + stringBuilder.AppendLine(emptyEntityContents); + } + + var targetfile = "tempEmptyPartials.cs"; + + System.IO.File.WriteAllText(targetfile, stringBuilder.ToString()); + + return targetfile; + } + + private static void AddCameraSetup(string projectDirectoryFullPath, List filesToCompile) + { + var cameraSetup = projectDirectoryFullPath + @"Setup\CameraSetup.cs"; + // load the camera setup first: + filesToCompile.Add(cameraSetup); + } + + private string CreateEmptyPartialForScreen(string projectName, string screenName) + { + return $@" +namespace {projectName}.Screens +{{ + public partial class {screenName} + {{ + void CustomInitialize(){{}} + void CustomActivity(bool firstTimeCalled){{}} + void CustomDestroy(){{}} + static void CustomLoadStaticContent(string contentManagerName){{}} + }} +}} +"; + } + + private string CreateEmptyPartialForEntity(string projectName, string entityName) + { + return $@" +namespace {projectName}.Entities +{{ + public partial class {entityName} + {{ + void CustomInitialize(){{}} + void CustomActivity(){{}} + void CustomDestroy(){{}} + static void CustomLoadStaticContent(string contentManagerName){{}} + }} +}} +"; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Scripting.Extensions.cs b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.Extensions.cs new file mode 100644 index 000000000..070536442 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.Extensions.cs @@ -0,0 +1,295 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Remoting.Lifetime; +using System.Threading; +using System.Threading.Tasks; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios requiring asynchronous script execution as well as unloading the +// scripts being executed. +// AsyncSamples +// Samples demonstrate the use of Async and Await mechanism available in C# 5 and higher. Note that the async method extensions +// cover the complete set of CSScript.Evaluator methods. +// +// UnloadingSamples +// Samples demonstrate the use of temporary AppDoamain for loading and executing dynamic C# code (script). It is the +// only mechanism available for unloading dynamically loaded assemblies. This is a well known CLR design limitation that leads to +// memory leaks if the assembly/script loaded in the caller AppDomain. The problem affects all C# script engines (e.g. Roslyn, CodeDom) +// and it cannot be solved by the engine itself thus CS-Script provides a work around in form of the MethodExtensions for the +// CSScript.Evaluator methods that are compatible with the unloading mechanism. +// +// Nevertheless you should try to avoid using remote AppDoamain unless you have to. It is very heavy and also imposes the serialization +// constrains. +// +// All samples rely on the compiler agnostic CSScript.Evaluator API. +namespace CSScriptEvaluatorExtensions +{ + public class HostApp + { + public static void Test() + { + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing asynchronous API"); + Console.WriteLine("---------------------------------------------"); + new AsyncSamples().RunAll(); + Thread.Sleep(2000); + Console.WriteLine("\nPress 'Enter' to run uloading samples..."); + Console.ReadLine(); + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing unloading API"); + Console.WriteLine("---------------------------------------------"); + new UnloadingSamples().RunAll(); + } + + class AsyncSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name); }; + + run(LoadDelegateAsync, "Start of " + nameof(LoadDelegateAsync)); + run(LoadMethodAsync, "Start of " + nameof(LoadMethodAsync)); + run(LoadCodeAsync, "Start of " + nameof(LoadCodeAsync)); + run(CreateDelegateAsync, "Start of " + nameof(CreateDelegateAsync)); + run(CompileCodeAsync, "Start of " + nameof(CompileCodeAsync)); + run(RemoteAsynch, "Start of " + nameof(RemoteAsynch)); + } + + async void LoadDelegateAsync() + { + var product = await CSScript.Evaluator + .LoadDelegateAsync>( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadDelegateAsync), product(4, 2)); + } + + public async void LoadMethodAsync() + { + dynamic script = await CSScript.Evaluator + .LoadMethodAsync(@"public int Sum(int a, int b) + { + return a + b; + } + public int Div(int a, int b) + { + return a/b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadMethodAsync), script.Div(15, 3)); + } + + public async void LoadCodeAsync() + { + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = await CSScript.Evaluator + .LoadCodeAsync( + @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadCodeAsync), calc.Sum(1, 2)); + } + + public async void CreateDelegateAsync() + { + var product = await CSScript.Evaluator + .CreateDelegateAsync( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(CreateDelegateAsync), product(15, 3)); + } + + public async void CompileCodeAsync() + { + Assembly script = await CSScript.Evaluator + .CompileCodeAsync(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + dynamic calc = script.CreateObject("*"); + + Console.WriteLine(" End of {0}: {1}", nameof(CompileCodeAsync), calc.Sum(15, 3)); + } + + public async void RemoteAsynch() + { + var sum = await Task.Run(() => + CSScript.Evaluator + .CreateDelegateRemotely( + @"int Sum(int a, int b) + { + return a+b; + }") + ); + Console.WriteLine(" End of {0}: {1}", nameof(RemoteAsynch), sum(1, 2)); + + sum.UnloadOwnerDomain(); + } + } + + class UnloadingSamples + { + public void RunAll() + { + CreateDelegateRemotely(); + LoadMethodRemotely(); + LoadCodeRemotely(); + LoadCodeRemotelyWithInterface(); + } + + public void CreateDelegateRemotely() + { + var sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + Console.WriteLine("{0}: {1}", nameof(CreateDelegateRemotely), sum(15, 3)); + + sum.UnloadOwnerDomain(); + } + + public void LoadCodeRemotely() + { + // Class Calc doesn't implement ICals interface. Thus the compiled object cannot be typecasted into + // the interface and Evaluator will emit duck-typed assembly instead. + // But Mono and Roslyn build file-less assemblies, meaning that they cannot be used to build + // duck-typed proxies and CodeDomEvaluator needs to be used explicitly. + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.CodeDomEvaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject + { + object t; + public int Sum(int a, int b) + { + t = new Test(); + return a+b; + } + } + + class Test + { + ~Test() + { + Console.WriteLine(""Domain is unloaded: ~Test()""); + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadCodeRemotelyWithInterface() + { + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + var script = CSScript.Evaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject, CSScriptEvaluatorExtensions.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotelyWithInterface), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadMethodRemotely() + { + // LoadMethodRemotely is essentially the same as LoadCodeRemotely. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.Evaluator + .LoadMethodRemotely( + @"public int Sum(int a, int b) + { + return a+b; + } + public int Sub(int a, int b) + { + return a-b; + }"); + + Console.WriteLine("{0}: {1}", nameof(LoadMethodRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + MethodDelegate sum; + ClientSponsor sumSponsor; + + public void KeepRemoteObjectAlive() + { + sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + //Normally remote objects are disposed if they are not accessed withing a default timeout period. + //It is not even enough to keep transparent proxies or their wrappers (e.g. 'sum') referenced. + //To prevent GC collection in the remote domain use .NET ClientSponsor mechanism as below. + sumSponsor = sum.ExtendLifeFromMinutes(30); + } + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public interface IFullCalc + { + int Sum(int a, int b); + + int Sub(int a, int b); + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Scripting.evaluator.cs b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.evaluator.cs new file mode 100644 index 000000000..0e195776f --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.evaluator.cs @@ -0,0 +1,345 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Evaluator interface (API). +// This API is a unified generic interface allowing dynamic switch of the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// Apart from Evaluator (compiler agnostic) API CS-Script offers alternative hosting model: CS-Script Native, +// which relies solely on CodeDom compiler. CS-Script Native offers some features that are not available with CS-Script Evaluator +// (e.g. script unloading). +// +// The choice of the underlying compiling engine (e.g. Mono vs CodeDom) when using CS-Script Evaluator is always dictated by the +// specifics of the hosting scenario. Thanks to in-process compiler hosting, Mono and Roslyn demonstrate much better compiling +// performance comparing to CodeDom engine. However they don't allow script debugging and caching easily supported with CodeDom. +// Mono and particularly Roslyn also leas create more memory pressure due to the higher volume of the temp assemblies loaded into +// the hosting AppDomain. Roslyn (at least CSharp.Scripting-v1.2.0.0) also has very high initial loading overhead up to 4 seconds. +// +// One of the possible approaches would be to use EvaluatorEngine.CodeDom during the active development and later on switch to Mono/Roslyn. + +namespace CSScriptEvaluatorApi +{ + public class HostApp + { + public static void Test() + { + // Just in case clear AlternativeCompiler so it is not set to Roslyn or anything else by + // the CS-Script installed (if any) on the host OS + CSScript.GlobalSettings.UseAlternativeCompiler = null; + + var samples = new EvaluatorSamples(); + + Console.WriteLine("Testing compiling services"); + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + + samples.RunAll(); + + //samples.DebugTest(); //uncomment if want to fire an assertion during the script execution + + //Profile(); //uncomment if want to test performance of the engines + } + + class EvaluatorSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name + " - OK"); }; + + run(CompileMethod_Instance, nameof(CompileMethod_Instance)); + run(CompileMethod_Static, nameof(CompileMethod_Static)); + run(CreateDelegate, nameof(CreateDelegate)); + run(LoadDelegate, nameof(LoadDelegate)); + run(LoadCode, nameof(LoadCode)); + run(LoadMethod, nameof(LoadMethod)); + run(LoadMethodWithInterface, nameof(LoadMethodWithInterface)); + run(LoadCode_WithInterface, nameof(LoadCode_WithInterface)); + run(LoadCode_WithDuckTypedInterface, nameof(LoadCode_WithDuckTypedInterface)); + } + + public void CompileMethod_Instance() + { + // 1- CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.Evaluator + .CompileMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public void CompileMethod_Static() + { + // 1 - CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - GetStaticMethod returns duck-typed delegate that accepts 'params object[]' arguments + // Note: GetStaticMethodWithArgs can be replaced with a more convenient/shorter version + // that takes the object instead of the Type and then queries objects type internally: + // "GetStaticMethod("*.Test", data)" + + var test = CSScript.Evaluator + .CompileMethod(@"using CSScriptEvaluatorApi; + static void Test(InputData data) + { + data.Index = GetIndex(); + } + static int GetIndex() + { + return Environment.TickCount; + }") + .GetStaticMethodWithArgs("*.Test", typeof(InputData)); + + var data = new InputData(); + test(data); + } + + public void CreateDelegate() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sqr = CSScript.Evaluator + .CreateDelegate(@"int Sqr(int a) + { + return a * a; + }"); + + var r = sqr(3); + } + + public void LoadDelegate() + { + // Wraps method into a class definition, loads the compiled assembly + // and returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + var product = CSScript.Evaluator + .LoadDelegate>( + @"int Product(int a, int b) + { + return a * b; + }"); + + int result = product(3, 2); + } + + public void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethod() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. + // 'public' is optional as it will be injected if the code doesn't start with it. + dynamic script = CSScript.Evaluator + .LoadMethod(@"using System; + public int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethodWithInterface() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.Evaluator + .LoadMethod( + @"int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2 - The script class implements host app interface so the returned object can be type casted into it + + var script = (ICalc)CSScript.Evaluator + .LoadCode(@"using System; + public class Script : CSScriptEvaluatorApi.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithDuckTypedInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.MonoEvaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void PerformanceTest(int count = -1) + { + var code = @"int Sqr(int a) + { + return a * a; + }"; + + if (count != -1) + code += "//" + count; //this unique extra code comment ensures the code to be compiled cannot be cached + + dynamic script = CSScript.Evaluator + .CompileMethod(code) + .CreateObject("*"); + + var r = script.Sqr(3); + } + + public void DebugTest() + { + //pops up an assertion dialog + + CSScript.EvaluatorConfig.DebugBuild = true; + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }"); + + var r = script.Sum(3, 4); + } + } + + public static void Profile() + { + var sw = new Stopwatch(); + var samples = new EvaluatorSamples(); + var count = 20; + var inxed = 0; + bool preventCaching = false; + + Action run = () => + { + sw.Restart(); + for (int i = 0; i < count; i++) + if (preventCaching) + samples.PerformanceTest(inxed++); + else + samples.PerformanceTest(); + + Console.WriteLine(CSScript.Evaluator.GetType().Name + ": " + sw.ElapsedMilliseconds); + }; + + Action runAll = () => + { + Console.WriteLine("\n---------------------------------------------"); + Console.WriteLine($"Caching enabled: {!preventCaching}\n"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + run(); + }; + + RoslynEvaluator.LoadCompilers(); //Roslyn is extremely heavy so exclude startup time from profiling + + Console.WriteLine("Testing performance"); + + preventCaching = true; + runAll(); + + preventCaching = false; + runAll(); + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public class InputData + { + public int Index = 0; + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Scripting.native.cs b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.native.cs new file mode 100644 index 000000000..37c7ef2f7 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Scripting.native.cs @@ -0,0 +1,354 @@ +using CSScriptLibrary; +using System; +using System.Linq; +using System.IO; +using csscript; +using System.CodeDom.Compiler; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Native interface (API). +// This API is a compiler specific interface, which relies solely on CodeDom compiler. In most of the cases +// CS-Script Native model is the most flexible and natural choice +// +// Apart from Native API CS-Script offers alternative hosting model: CS-Script Evaluator, which provides +// a unified generic interface allowing dynamic switch the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// The Native interface is the original API that was designed to take maximum advantage of the dynamic C# code +// execution with CodeDom. The original implementation of this API was developed even before any compiler-as-service +// solution became available. Being based solely on CodeDOM the API doesn't utilize neither Mono nor Roslyn +// scripting solutions. Despite that CS-Script Native is the most mature, powerful and flexible API available with CS-Script. +// +// Native interface allows some unique features that are not available with CS-Script Evaluator: +// - Debugging scripts +// - Script caching +// - Script unloading + +namespace CSScriptNativeApi +{ + public class HostApp + { + public static void Test() + { + var host = new HostApp(); + host.Log("Testing compiling services CS-Script Native API"); + Console.WriteLine("---------------------------------------------"); + + CodeDomSamples.LoadMethod_Instance(); + CodeDomSamples.LoadMethod_Static(); + CodeDomSamples.LoadDelegate(); + CodeDomSamples.CreateAction(); + CodeDomSamples.CreateFunc(); + CodeDomSamples.LoadCode(); + CodeDomSamples.LoadCode_WithInterface(host); + CodeDomSamples.LoadCode_WithDuckTypedInterface(host); + CodeDomSamples.ExecuteAndUnload(); + //CodeDomSamples.DebugTest(); //uncomment if want to fire an assertion during the script execution + } + + public class CodeDomSamples + { + public static void LoadMethod_Instance() + { + // 1- LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.LoadMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public static void LoadMethod_Static() + { + // 1 - LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - GetStaticMethod returns first found static method as a duck-typed delegate that + // accepts 'params object[]' arguments + // + // Note: you can use GetStaticMethodWithArgs for higher precision method search: GetStaticMethodWithArgs("*.SayHello", typeof(string)); + var sayHello = CSScript.LoadMethod(@"static void SayHello(string greeting) + { + Console.WriteLine(greeting); + }") + .GetStaticMethod(); + + sayHello("Hello World!"); + } + + public static void LoadDelegate() + { + // LoadDelegate wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + // The 'using System;' is optional; it demonstrates how to specify 'using' in the method-only syntax + + var sayHello = CSScript.LoadDelegate>( + @"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateAction() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sayHello = CSScript.CreateAction(@"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateFunc() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // int as a return type. + + var Sqr = CSScript.CreateFunc(@"int Sqr(int a) + { + return a * a; + }"); + int r = Sqr(3); + } + + public static void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + + public static void LoadCodeWithConfig() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + string file = Path.GetTempFileName(); + try + { + File.WriteAllText(file, @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + var settings = new Settings(); + //settings = null; // set to null to foll back to defaults + + dynamic script = CSScript.LoadWithConfig(file, null, false, settings, "/define:TEST") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + finally + { + if (File.Exists(file)) + File.Delete(file); + } + } + + public static void LoadCode_WithInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly. + // 2 - The script class implements host app interface so the returned object can be type casted into it. + // 3 - In this sample host object is passed into script routine. + + var calc = (ICalc) CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script : ICalc + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*"); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void LoadCode_WithDuckTypedInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + // 3 - In this sample host object is passed into script routine. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*") + .AlignToInterface(); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void ExecuteAndUnload() + { + // The script will be loaded into a temporary AppDomain and unloaded after the execution. + + // Note: remote execution is a subject of some restrictions associated with the nature of the + // CLR cross-AppDomain interaction model: + // * the script class must be serializable or derived from MarshalByRefObject. + // + // * any object (call arguments, return objects) that crosses ApPDomain boundaries + // must be serializable or derived from MarshalByRefObject. + // + // * long living script class instances may get disposed in remote domain even if they are + // being referenced in the current AppDomain. You need to use the usual .NET techniques + // to prevent that. See LifetimeManagement.cs sample for details. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var code = @"using System; + public class Script : MarshalByRefObject + { + public void Hello(string greeting) + { + Console.WriteLine(greeting); + } + }"; + + //Note: usage of helper.CreateAndAlignToInterface("Script") is also acceptable + using (var helper = new AsmHelper(CSScript.CompileCode(code), null, deleteOnExit: true)) + { + IScript script = helper.CreateAndAlignToInterface("*"); + script.Hello("Hi there..."); + } + //from this point AsmHelper is disposed and the temp AppDomain is unloaded + } + + public static void DebugTest() + { + //pops up an assertion dialog + dynamic script = CSScript.LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }", null, debugBuild: true).CreateObject("*"); + + int result = script.Sum(1, 2); + } + } + + public void Log(string message) + { + Console.WriteLine(message); + } + } + + public interface IScript + { + void Hello(string greeting); + } + + public interface ICalc + { + HostApp Host { get; set; } + + int Sum(int a, int b); + } + + public class Samples + { + static public void CompilingHistory() + { + string script = Path.GetTempFileName(); + string scriptAsm = script + ".dll"; + CSScript.KeepCompilingHistory = true; + + try + { + File.WriteAllText(script, @"using System; + using System.Windows.Forms; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + + + CSScript.CompileFile(script, scriptAsm, false, null); + + CompilingInfo info = CSScript.CompilingHistory + .Values + .FirstOrDefault(item => item.ScriptFile == script); + if (info != null) + { + Console.WriteLine("Script: " + info.ScriptFile); + + Console.WriteLine("Referenced assemblies:"); + foreach (string asm in info.Input.ReferencedAssemblies) + Console.WriteLine(asm); + + if (info.Result.Errors.HasErrors) + { + foreach (CompilerError err in info.Result.Errors) + if (!err.IsWarning) + Console.WriteLine("Error: " + err.ErrorText); + } + } + + CSScript.CompilingHistory.Clear(); + + } + finally + { + CSScript.KeepCompilingHistory = false; + } + } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/Setup/CameraSetup.cs b/FRBDK/GlueView2/FlatRedBallWpf/Setup/CameraSetup.cs new file mode 100644 index 000000000..88f4f1e77 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/Setup/CameraSetup.cs @@ -0,0 +1,148 @@ + // This is a generated file created by Glue. To change this file, edit the camera settings in Glue. + // To access the camera settings, push the camera icon. + using Camera = FlatRedBall.Camera; + namespace FlatRedBallWpf + { + public class CameraSetupData + { + public float Scale { get; set; } + public bool Is2D { get; set; } + public int ResolutionWidth { get; set; } + public int ResolutionHeight { get; set; } + public decimal? AspectRatio { get; set; } + public bool AllowWidowResizing { get; set; } + public bool IsFullScreen { get; set; } + public ResizeBehavior ResizeBehavior { get; set; } + public WidthOrHeight DominantInternalCoordinates { get; set; } + } + public enum ResizeBehavior + { + StretchVisibleArea, + IncreaseVisibleArea + } + public enum WidthOrHeight + { + Width, + Height + } + internal static class CameraSetup + { + static Microsoft.Xna.Framework.GraphicsDeviceManager graphicsDeviceManager; + public static CameraSetupData Data = new CameraSetupData + { + Scale = 100f, + ResolutionWidth = 800, + ResolutionHeight = 600, + Is2D = true, + IsFullScreen = false, + AllowWidowResizing = false, + ResizeBehavior = ResizeBehavior.StretchVisibleArea, + DominantInternalCoordinates = WidthOrHeight.Height, + } + ; + internal static void ResetCamera (Camera cameraToReset = null) + { + if (cameraToReset == null) + { + cameraToReset = FlatRedBall.Camera.Main; + } + cameraToReset.Orthogonal = Data.Is2D; + if (Data.Is2D) + { + cameraToReset.OrthogonalHeight = Data.ResolutionHeight; + cameraToReset.OrthogonalWidth = Data.ResolutionWidth; + cameraToReset.FixAspectRatioYConstant(); + } + if (Data.AspectRatio != null) + { + SetAspectRatioTo(Data.AspectRatio.Value, Data.DominantInternalCoordinates, Data.ResolutionWidth, Data.ResolutionHeight); + } + } + internal static void SetupCamera (Camera cameraToSetUp, Microsoft.Xna.Framework.GraphicsDeviceManager graphicsDeviceManager) + { + CameraSetup.graphicsDeviceManager = graphicsDeviceManager; + ResetWindow(); + ResetCamera(cameraToSetUp); + FlatRedBall.FlatRedBallServices.GraphicsOptions.SizeOrOrientationChanged += HandleResolutionChange; + } + internal static void ResetWindow () + { + #if WINDOWS || DESKTOP_GL + FlatRedBall.FlatRedBallServices.Game.Window.AllowUserResizing = Data.AllowWidowResizing; + if (Data.IsFullScreen) + { + #if DESKTOP_GL + graphicsDeviceManager.HardwareModeSwitch = false; + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution(Microsoft.Xna.Framework.Graphics.GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, Microsoft.Xna.Framework.Graphics.GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height, FlatRedBall.Graphics.WindowedFullscreenMode.FullscreenBorderless); + #elif WINDOWS + System.IntPtr hWnd = FlatRedBall.FlatRedBallServices.Game.Window.Handle; + var control = System.Windows.Forms.Control.FromHandle(hWnd); + var form = control.FindForm(); + form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + form.WindowState = System.Windows.Forms.FormWindowState.Maximized; + #endif + } + else + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + } + #elif IOS || ANDROID + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetFullScreen(FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth, FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight); + #elif UWP + if (Data.IsFullScreen) + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetFullScreen(Data.ResolutionWidth, Data.ResolutionHeight); + } + else + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + var newWindowSize = new Windows.Foundation.Size((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TryResizeView(newWindowSize); + } + #endif + } + private static void HandleResolutionChange (object sender, System.EventArgs args) + { + if (Data.AspectRatio != null) + { + SetAspectRatioTo(Data.AspectRatio.Value, Data.DominantInternalCoordinates, Data.ResolutionWidth, Data.ResolutionHeight); + } + if (Data.Is2D && Data.ResizeBehavior == ResizeBehavior.IncreaseVisibleArea) + { + FlatRedBall.Camera.Main.OrthogonalHeight = FlatRedBall.Camera.Main.DestinationRectangle.Height / (Data.Scale/ 100.0f); + FlatRedBall.Camera.Main.FixAspectRatioYConstant(); + } + } + private static void SetAspectRatioTo (decimal aspectRatio, WidthOrHeight dominantInternalCoordinates, int desiredWidth, int desiredHeight) + { + var resolutionAspectRatio = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth / (decimal)FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight; + int destinationRectangleWidth; + int destinationRectangleHeight; + int x = 0; + int y = 0; + if (aspectRatio > resolutionAspectRatio) + { + destinationRectangleWidth = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth; + destinationRectangleHeight = FlatRedBall.Math.MathFunctions.RoundToInt(destinationRectangleWidth / (float)aspectRatio); + y = (FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight - destinationRectangleHeight) / 2; + } + else + { + destinationRectangleHeight = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight; + destinationRectangleWidth = FlatRedBall.Math.MathFunctions.RoundToInt(destinationRectangleHeight * (float)aspectRatio); + x = (FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth - destinationRectangleWidth) / 2; + } + FlatRedBall.Camera.Main.DestinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, destinationRectangleWidth, destinationRectangleHeight); + if (dominantInternalCoordinates == WidthOrHeight.Height) + { + FlatRedBall.Camera.Main.OrthogonalHeight = desiredHeight; + FlatRedBall.Camera.Main.FixAspectRatioYConstant(); + } + else + { + FlatRedBall.Camera.Main.OrthogonalWidth = desiredWidth; + FlatRedBall.Camera.Main.FixAspectRatioXConstant(); + } + } + } + } diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs new file mode 100644 index 000000000..9ae900d11 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs @@ -0,0 +1,87 @@ +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + public class CollidableListVsTileShapeCollectionRelationship : + CollisionRelationship + where FirstCollidableT : PositionedObject, ICollidable + { + CollidableVsTileShapeCollectionData data; + + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCircle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionRectangle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionPolygon = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCollidable = subCollisionFunc; } + + public Action CollisionOccurred; + + PositionedObjectList list; + + public override object FirstAsObject => list; + public override object SecondAsObject => data.TileShapeCollection; + + public CollidableListVsTileShapeCollectionRelationship(PositionedObjectList list, TileShapeCollection tileShapeCollection) + { + data = new CollidableVsTileShapeCollectionData(tileShapeCollection); + this.list = list; + } + + public override bool DoCollisions() + { + bool didCollisionOccur = false; + if (skippedFrames < FrameSkip) + { + skippedFrames++; + } + else + { + if (CollisionLimit == CollisionLimit.Closest || CollisionLimit == CollisionLimit.First) + { + string message = $"{nameof(CollidableVsTileShapeCollectionRelationship)} does not implement CollisionLimit {CollisionLimit}"; + throw new NotImplementedException(); + } + else + { + skippedFrames = 0; + + for(int i = list.Count - 1 ; i > -1; i--) + { + var singleObject = list[i]; + + var didCollide = false; + // todo - tile shape collections need to report their deep collision, they don't currently: + if (CollisionType == CollisionType.EventOnlyCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionEventOnly(singleObject); + } + else if (CollisionType == CollisionType.MoveCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionMove(singleObject); + } + else if (CollisionType == CollisionType.BounceCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionBounce(singleObject, bounceElasticity); + } + else + { + throw new NotImplementedException(); + } + + if (didCollide) + { + didCollisionOccur = true; + CollisionOccurred?.Invoke(singleObject, data.TileShapeCollection); + } + } + } + } + return didCollisionOccur; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs new file mode 100644 index 000000000..20a06ca64 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs @@ -0,0 +1,189 @@ +using FlatRedBall; +using FlatRedBall.Math.Collision; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + class CollidableVsTileShapeCollectionData + where FirstCollidableT : PositionedObject, ICollidable + { + TileShapeCollection tileShapeCollection; + public TileShapeCollection TileShapeCollection { get { return tileShapeCollection; } } + + public Func firstSubCollisionCircle; + public Func firstSubCollisionRectangle; + public Func firstSubCollisionPolygon; + public Func firstSubCollisionCollidable; + + public CollidableVsTileShapeCollectionData(TileShapeCollection tileShapeCollection) + { + this.tileShapeCollection = tileShapeCollection; + } + + public bool CollideAgainstConsiderSubCollisionEventOnly(FirstCollidableT singleObject) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainst(circle); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainst(rectangle); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainst(polygon); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainst(collidable); + } + else + { + return this.tileShapeCollection.CollideAgainst(singleObject); + } + } + + public bool CollideAgainstConsiderSubCollisionMove(FirstCollidableT singleObject) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(circle); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(rectangle); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(polygon); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(collidable); + } + else + { + return this.tileShapeCollection.CollideAgainstSolid(singleObject); + } + } + + public bool CollideAgainstConsiderSubCollisionBounce(FirstCollidableT singleObject, float bounceElasticity) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(circle, bounceElasticity); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(rectangle, bounceElasticity); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(polygon, bounceElasticity); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(collidable, bounceElasticity); + } + else + { + return this.tileShapeCollection.CollideAgainstBounce(singleObject, bounceElasticity); + } + } + } + + + public class CollidableVsTileShapeCollectionRelationship : CollisionRelationship + where FirstCollidableT : PositionedObject, ICollidable + { + CollidableVsTileShapeCollectionData data; + + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCircle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionRectangle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionPolygon = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCollidable = subCollisionFunc; } + + public Action CollisionOccurred; + + + FirstCollidableT singleObject; + + public override object FirstAsObject => singleObject; + public override object SecondAsObject => data.TileShapeCollection; + + public CollidableVsTileShapeCollectionRelationship(FirstCollidableT singleObject, TileShapeCollection tileShapeCollection) + { + data = new CollidableVsTileShapeCollectionData(tileShapeCollection); + this.singleObject = singleObject; + } + + public override bool DoCollisions() + { + bool didCollisionOccur = false; + + if (skippedFrames < FrameSkip) + { + skippedFrames++; + } + else + { + if (CollisionLimit == CollisionLimit.Closest || CollisionLimit == CollisionLimit.First) + { + string message = $"{nameof(CollidableVsTileShapeCollectionRelationship)} does not implement CollisionLimit {CollisionLimit}"; + throw new NotImplementedException(); + } + else + { + skippedFrames = 0; + + var didCollide = false; + // todo - tile shape collections need to report their deep collision, they don't currently: + if (CollisionType == CollisionType.EventOnlyCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionEventOnly(singleObject); + } + else if (CollisionType == CollisionType.MoveCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionMove(singleObject); + } + else if (CollisionType == CollisionType.BounceCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionBounce(singleObject, bounceElasticity); + } + else + { + throw new NotImplementedException(); + } + + if(didCollide) + { + CollisionOccurred?.Invoke(singleObject, data.TileShapeCollection); + + didCollisionOccur = true; + } + } + } + + return didCollisionOccur; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs new file mode 100644 index 000000000..56be148cb --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs @@ -0,0 +1,41 @@ +using FlatRedBall.Math.Collision; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + public static class CollisionManagerTileShapeCollectionExtensions + { + public static CollidableVsTileShapeCollectionRelationship CreateTileRelationship( + this CollisionManager collisionManager, + FirstCollidableT collidable, TileShapeCollection tileShapeCollection) + where FirstCollidableT : PositionedObject, ICollidable + { + var relationship = new CollidableVsTileShapeCollectionRelationship( + collidable, tileShapeCollection); + + CollisionManager.Self.Relationships.Add(relationship); + + return relationship; + } + + public static CollidableListVsTileShapeCollectionRelationship CreateTileRelationship( + this CollisionManager collisionManager, + PositionedObjectList collidable, TileShapeCollection tileShapeCollection) + where FirstCollidableT : PositionedObject, ICollidable + { + var relationship = new CollidableListVsTileShapeCollectionRelationship( + collidable, tileShapeCollection); + + CollisionManager.Self.Relationships.Add(relationship); + + return relationship; + } + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/TileShapeCollection.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/TileShapeCollection.cs new file mode 100644 index 000000000..81acffd97 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileCollisions/TileShapeCollection.cs @@ -0,0 +1,930 @@ +using FlatRedBall.Math; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileGraphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FlatRedBall.TileCollisions +{ + public partial class TileShapeCollection + { + #region Fields + + ShapeCollection mShapes; + Axis mSortAxis = Axis.X; + float mLeftSeedX = 0; + float mBottomSeedY = 0; + float mGridSize; + bool mVisible = true; + + + bool mFirstTimeSortAxisSet = true; + + #endregion + + #region Properties + + public Axis SortAxis + { + get + { + return mSortAxis; + } + set + { + bool hasChanged = value != mSortAxis; + if (hasChanged || mFirstTimeSortAxisSet) + { + mSortAxis = value; + PerformSort(); + } + } + } + + public float GridSize + { + get { return mGridSize; } + set + { +#if DEBUG + if (value < 0) + { + throw new Exception("GridSize needs to be positive"); + } +#endif + + + mGridSize = value; + mShapes.MaxAxisAlignedRectanglesScale = mGridSize; + } + } + + public PositionedObjectList Rectangles + { + get { return mShapes.AxisAlignedRectangles; } + } + + + public PositionedObjectList Polygons + { + get { return mShapes.Polygons; } + } + + public string Name { get; set; } + + + public List LastCollisionPolygons => mShapes.LastCollisionPolygons; + public List LastCollisionAxisAlignedRectangles => mShapes.LastCollisionAxisAlignedRectangles; + + public bool Visible + { + get { return mVisible; } + set + { + mVisible = value; + for (int i = 0; i < mShapes.AxisAlignedRectangles.Count; i++) + { + mShapes.AxisAlignedRectangles[i].Visible = value; + } + for (int i = 0; i < mShapes.Polygons.Count; i++) + { + if (value) + { + // to get the verts to show up + mShapes.Polygons[i].ForceUpdateDependencies(); + } + mShapes.Polygons[i].Visible = value; + } + } + } + + #endregion + + public TileShapeCollection() + { + mShapes = new ShapeCollection(); + GridSize = 16; + } + + + public void AddToLayer(FlatRedBall.Graphics.Layer layer) + { + this.mShapes.AddToManagers(layer); + } + + public bool CollideAgainstSolid(AxisAlignedRectangle movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Circle movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Polygon movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Line movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainst(AxisAlignedRectangle rectangle) + { + return mShapes.CollideAgainst(rectangle, true, mSortAxis); + } + + public bool CollideAgainst(Circle circle) + { + return mShapes.CollideAgainst(circle, true, mSortAxis); + } + + public bool CollideAgainst(Polygon polygon) + { + return mShapes.CollideAgainst(polygon, true, mSortAxis); + } + + public bool CollideAgainst(Line line) + { + return mShapes.CollideAgainst(line, true, mSortAxis); + } + + public bool CollideAgainst(ICollidable collidable) + { + return mShapes.CollideAgainst(collidable.Collision, true, mSortAxis); + } + + public bool CollideAgainstSolid(ICollidable collidable) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(collidable.Collision, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstBounce(ICollidable collidable, float elasticity) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(collidable.Collision, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(AxisAlignedRectangle rectangle, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(rectangle, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(Circle circle, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(circle, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(Polygon polygon, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(polygon, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + [Obsolete("Use GetRectangleAtPosition instead as it more clearly indicates what the method does.")] + public AxisAlignedRectangle GetTileAt(float x, float y) + { + return GetRectangleAtPosition(x, y); + } + + public AxisAlignedRectangle GetRectangleAtPosition(float worldX, float worldY) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + float keyValue = GetCoordinateValueForPartitioning(middleOfTileX, middleOfTileY); + + float keyValueBefore = keyValue - GridSize / 2.0f; + float keyValueAfter = keyValue + GridSize / 2.0f; + + int startInclusive = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValueBefore, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + + int endExclusive = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValueAfter, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + AxisAlignedRectangle toReturn = GetRectangleAtPosition(worldX, worldY, startInclusive, endExclusive); + + return toReturn; + } + + public Polygon GetPolygonAtPosition(float worldX, float worldY) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + float keyValue = GetCoordinateValueForPartitioning(middleOfTileX, middleOfTileY); + + var halfGridSize = GridSize / 2.0f; + + float keyValueBefore = keyValue - halfGridSize; + float keyValueAfter = keyValue + halfGridSize; + + int startInclusive = mShapes.Polygons.GetFirstAfter(keyValueBefore, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + + int endExclusive = mShapes.Polygons.GetFirstAfter(keyValueAfter, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + var left = middleOfTileX - halfGridSize; + var right = middleOfTileX + halfGridSize; + var top = middleOfTileY + halfGridSize; + var bottom = middleOfTileY - halfGridSize; + + for (int i = startInclusive; i < endExclusive; i++) + { + var polygon = mShapes.Polygons[i]; + + if (polygon.Position.X > left && polygon.Position.X < right && + polygon.Position.Y > bottom && polygon.Position.Y < top) + { + return polygon; + } + } + + return null; + } + + private Polygon GetPolygonAtPosition(float worldX, float worldY, int startInclusive, int endExclusive) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + + var halfGridSize = GridSize / 2.0f; + + var left = middleOfTileX - halfGridSize; + var right = middleOfTileX + halfGridSize; + var top = middleOfTileY + halfGridSize; + var bottom = middleOfTileY - halfGridSize; + + for (int i = startInclusive; i < endExclusive; i++) + { + var polygon = mShapes.Polygons[i]; + + if (polygon.Position.X > left && polygon.Position.X < right && + polygon.Position.Y > bottom && polygon.Position.Y < top) + { + return polygon; + } + } + + return null; + } + + private AxisAlignedRectangle GetRectangleAtPosition(float x, float y, int startInclusive, int endExclusive) + { + AxisAlignedRectangle toReturn = null; + for (int i = startInclusive; i < endExclusive; i++) + { + if (mShapes.AxisAlignedRectangles[i].IsPointInside(x, y)) + { + toReturn = mShapes.AxisAlignedRectangles[i]; + break; + } + } + return toReturn; + } + + public void AddCollisionAtWorld(float x, float y) + { + // Make sure there isn't already collision here + if (GetRectangleAtPosition(x, y) == null) + { + // x and y + // represent + // the center + // of the tile + // where the user + // may want to add + // collision. Let's + // subtract half width/ + // height so we can use the + // bottom/left + float roundedX = MathFunctions.RoundFloat(x - GridSize / 2.0f, GridSize, mLeftSeedX); + float roundedY = MathFunctions.RoundFloat(y - GridSize / 2.0f, GridSize, mBottomSeedY); + + AxisAlignedRectangle newAar = new AxisAlignedRectangle(); + newAar.Width = GridSize; + newAar.Height = GridSize; + newAar.Left = roundedX; + newAar.Bottom = roundedY; + + if (this.mVisible) + { + newAar.Visible = true; + } + + float keyValue = GetCoordinateValueForPartitioning(roundedX, roundedY); + + int index = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValue, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + mShapes.AxisAlignedRectangles.Insert(index, newAar); + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(newAar); + + newAar.RepositionDirections = directions; + } + } + + public void RemoveCollisionAtWorld(float x, float y) + { + AxisAlignedRectangle existing = GetTileAt(x, y); + if (existing != null) + { + ShapeManager.Remove(existing); + + float keyValue = GetCoordinateValueForPartitioning(existing.X, existing.Y); + + float keyValueBefore = keyValue - GridSize * 3 / 2.0f; + float keyValueAfter = keyValue + GridSize * 3 / 2.0f; + + int before = Rectangles.GetFirstAfter(keyValueBefore, mSortAxis, 0, Rectangles.Count); + int after = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Rectangles.Count); + + AxisAlignedRectangle leftOf = GetRectangleAtPosition(existing.X - GridSize, existing.Y, before, after); + AxisAlignedRectangle rightOf = GetRectangleAtPosition(existing.X + GridSize, existing.Y, before, after); + AxisAlignedRectangle above = GetRectangleAtPosition(existing.X, existing.Y + GridSize, before, after); + AxisAlignedRectangle below = GetRectangleAtPosition(existing.X, existing.Y - GridSize, before, after); + + if (leftOf != null && (leftOf.RepositionDirections & RepositionDirections.Right) != RepositionDirections.Right) + { + leftOf.RepositionDirections |= RepositionDirections.Right; + + } + if (rightOf != null && (rightOf.RepositionDirections & RepositionDirections.Left) != RepositionDirections.Left) + { + rightOf.RepositionDirections |= RepositionDirections.Left; + } + + if (above != null && (above.RepositionDirections & RepositionDirections.Down) != RepositionDirections.Down) + { + above.RepositionDirections |= RepositionDirections.Down; + } + + if (below != null && (below.RepositionDirections & RepositionDirections.Up) != RepositionDirections.Up) + { + below.RepositionDirections |= RepositionDirections.Up; + } + + + } + + + } + + public void RemoveSurroundedCollision() + { + for (int i = Rectangles.Count - 1; i > -1; i--) + { + var rectangle = Rectangles[i]; + if (rectangle.RepositionDirections == RepositionDirections.None) + { + rectangle.Visible = false; + this.Rectangles.Remove(rectangle); + } + } + } + + + private float GetCoordinateValueForPartitioning(float x, float y) + { + float keyValue = 0; + + switch (mSortAxis) + { + case Axis.X: + keyValue = x; + break; + case Axis.Y: + keyValue = y; + break; + case Axis.Z: + throw new NotImplementedException("Sorting on Z not supported"); + } + return keyValue; + } + + private RepositionDirections UpdateRepositionForNeighborsAndGetThisRepositionDirection(PositionedObject positionedObject) + { + // Let's see what is surrounding this rectangle and update it and the surrounding rects appropriately + float keyValue = GetCoordinateValueForPartitioning(positionedObject.Position.X, positionedObject.Position.Y); + + float keyValueBefore = keyValue - GridSize * 3 / 2.0f; + float keyValueAfter = keyValue + GridSize * 3 / 2.0f; + + int rectanglesBeforeIndex = Rectangles.GetFirstAfter(keyValueBefore, mSortAxis, 0, Rectangles.Count); + int rectanglesAfterIndex = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Rectangles.Count); + + int polygonsBeforeIndex = Polygons.GetFirstAfter(keyValueBefore, mSortAxis, 0, Polygons.Count); + int polygonsAfterIndex = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Polygons.Count); + + + float leftOfX = positionedObject.Position.X - GridSize; + float rightOfX = positionedObject.Position.X + GridSize; + float middleX = positionedObject.Position.X; + + float aboveY = positionedObject.Position.Y + GridSize; + float belowY = positionedObject.Position.Y - GridSize; + float middleY = positionedObject.Position.Y; + + AxisAlignedRectangle rectangleLeftOf = GetRectangleAtPosition(leftOfX, middleY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleRightOf = GetRectangleAtPosition(rightOfX, middleY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleAbove = GetRectangleAtPosition(middleX, aboveY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleBelow = GetRectangleAtPosition(middleX, belowY, rectanglesBeforeIndex, rectanglesAfterIndex); + + RepositionDirections directions = RepositionDirections.All; + if (rectangleLeftOf != null) + { + directions -= RepositionDirections.Left; + if ((rectangleLeftOf.RepositionDirections & RepositionDirections.Right) == RepositionDirections.Right) + { + rectangleLeftOf.RepositionDirections -= RepositionDirections.Right; + } + } + else + { + var polygon = GetPolygonAtPosition(leftOfX, middleY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Left; + if ((polygon.RepositionDirections & RepositionDirections.Right) == RepositionDirections.Right) + { + polygon.RepositionDirections -= RepositionDirections.Right; + } + } + } + + if (rectangleRightOf != null) + { + directions -= RepositionDirections.Right; + + if ((rectangleRightOf.RepositionDirections & RepositionDirections.Left) == RepositionDirections.Left) + { + rectangleRightOf.RepositionDirections -= RepositionDirections.Left; + } + } + else + { + var polygon = GetPolygonAtPosition(rightOfX, middleY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Right; + if ((polygon.RepositionDirections & RepositionDirections.Left) == RepositionDirections.Left) + { + polygon.RepositionDirections -= RepositionDirections.Left; + } + } + } + + + + if (rectangleAbove != null) + { + directions -= RepositionDirections.Up; + + if ((rectangleAbove.RepositionDirections & RepositionDirections.Down) == RepositionDirections.Down) + { + rectangleAbove.RepositionDirections -= RepositionDirections.Down; + } + } + else + { + var polygon = GetPolygonAtPosition(middleX, aboveY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Up; + + if ((polygon.RepositionDirections & RepositionDirections.Down) == RepositionDirections.Down) + { + polygon.RepositionDirections -= RepositionDirections.Down; + } + } + } + + if (rectangleBelow != null) + { + directions -= RepositionDirections.Down; + if ((rectangleBelow.RepositionDirections & RepositionDirections.Up) == RepositionDirections.Up) + { + rectangleBelow.RepositionDirections -= RepositionDirections.Up; + } + } + else + { + var polygon = GetPolygonAtPosition(middleX, belowY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Down; + + if ((polygon.RepositionDirections & RepositionDirections.Up) == RepositionDirections.Up) + { + polygon.RepositionDirections -= RepositionDirections.Up; + } + } + } + + return directions; + } + + public void RemoveFromManagersOneWay() + { + this.mShapes.MakeOneWay(); + this.mShapes.RemoveFromManagers(); + this.mShapes.MakeTwoWay(); + } + + public void RemoveFromManagers() + { + this.mShapes.RemoveFromManagers(); + } + + private void PerformSort() + { + switch (mSortAxis) + { + case Axis.X: + mShapes.AxisAlignedRectangles.SortXInsertionAscending(); + mShapes.Polygons.SortXInsertionAscending(); + break; + case Axis.Y: + mShapes.AxisAlignedRectangles.SortYInsertionAscending(); + mShapes.Polygons.SortYInsertionAscending(); + break; + case Axis.Z: + mShapes.AxisAlignedRectangles.SortZInsertionAscending(); + mShapes.Polygons.SortZInsertionAscending(); + break; + } + } + + public void SetColor(Microsoft.Xna.Framework.Color color) + { + foreach (var rectangle in this.Rectangles) + { + rectangle.Color = color; + } + } + + public void RefreshAllRepositionDirections() + { + var count = this.mShapes.AxisAlignedRectangles.Count; + for (int i = 0; i < count; i++) + { + var rectangle = this.mShapes.AxisAlignedRectangles[i]; + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(rectangle); + + rectangle.RepositionDirections = directions; + } + + count = this.mShapes.Polygons.Count; + for (int i = 0; i < count; i++) + { + var polygon = this.mShapes.Polygons[i]; + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(polygon); + + polygon.RepositionDirections = directions; + } + } + + public override string ToString() + { + return Name; + } + } + + + + public static class TileShapeCollectionLayeredTileMapExtensions + { + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap, string nameToUse) + { + AddCollisionFrom(tileShapeCollection, layeredTileMap, + new List { nameToUse }); + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap, IEnumerable namesToUse) + { + Func, bool> predicate = (list) => + { + var nameProperty = list.FirstOrDefault(item => item.Name.ToLower() == "name"); + + return namesToUse.Contains(nameProperty.Value); + }; + + AddCollisionFrom(tileShapeCollection, layeredTileMap, predicate); + + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + foreach (var layer in layeredTileMap.MapLayers) + { + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + tileShapeCollection.AddCollisionAtWorld(centerX, + centerY); + } + } + } + } + } + } + + public static void AddMergedCollisionFrom(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + Dictionary> rectangleIndexes = new Dictionary>(); + + foreach (var layer in layeredTileMap.MapLayers) + { + AddCollisionFromLayerInternal(tileShapeCollection, predicate, properties, dimension, dimensionHalf, rectangleIndexes, layer); + } + + ApplyMerging(tileShapeCollection, dimension, rectangleIndexes); + } + + public static void AddMergedCollisionFromLayer(this TileShapeCollection tileShapeCollection, MapDrawableBatch layer, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + Dictionary> rectangleIndexes = new Dictionary>(); + + AddCollisionFromLayerInternal(tileShapeCollection, predicate, properties, dimension, dimensionHalf, rectangleIndexes, layer); + + ApplyMerging(tileShapeCollection, dimension, rectangleIndexes); + } + + public static void AddCollisionFromTilesWithProperty(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, string propertyName) + { + tileShapeCollection.AddCollisionFrom( + layeredTileMap, (list) => list.Any(item => item.Name == propertyName)); + + } + + public static void AddMergedCollisionFromTilesWithProperty(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, string propertyName) + { + tileShapeCollection.AddMergedCollisionFrom( + layeredTileMap, (list) => list.Any(item => item.Name == propertyName)); + + } + + private static void ApplyMerging(TileShapeCollection tileShapeCollection, float dimension, Dictionary> rectangleIndexes) + { + foreach (var kvp in rectangleIndexes.OrderBy(item => item.Key)) + { + var rectanglePositionList = kvp.Value.OrderBy(item => item).ToList(); + + var firstValue = rectanglePositionList[0]; + var currentValue = firstValue; + var expectedValue = firstValue + 1; + for (int i = 1; i < rectanglePositionList.Count; i++) + { + if (rectanglePositionList[i] != expectedValue) + { + CloseRectangle(tileShapeCollection, kvp.Key, dimension, firstValue, currentValue); + + firstValue = rectanglePositionList[i]; + currentValue = firstValue; + } + else + { + currentValue++; + } + + expectedValue = currentValue + 1; + } + + CloseRectangle(tileShapeCollection, kvp.Key, dimension, firstValue, currentValue); + } + } + + private static void AddCollisionFromLayerInternal(TileShapeCollection tileShapeCollection, Func, bool> predicate, Dictionary> properties, float dimension, float dimensionHalf, Dictionary> rectangleIndexes, MapDrawableBatch layer) + { + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + + int key; + int value; + + if (tileShapeCollection.SortAxis == Axis.X) + { + key = (int)(centerX / dimension); + value = (int)(centerY / dimension); + } + else if (tileShapeCollection.SortAxis == Axis.Y) + { + key = (int)(centerY / dimension); + value = (int)(centerX / dimension); + } + else + { + throw new NotImplementedException("Cannot add tile collision on z-sorted shape collections"); + } + + List listToAddTo = null; + if (rectangleIndexes.ContainsKey(key) == false) + { + listToAddTo = new List(); + rectangleIndexes.Add(key, listToAddTo); + } + else + { + listToAddTo = rectangleIndexes[key]; + } + listToAddTo.Add(value); + + } + } + } + } + } + + private static void CloseRectangle(TileShapeCollection tileShapeCollection, int keyIndex, float dimension, int firstValue, int currentValue) + { + float x = 0; + float y = 0; + float width = dimension; + float height = dimension; + + if (tileShapeCollection.SortAxis == Axis.X) + { + x = (keyIndex + .5f) * dimension; + } + else + { + // y moves down so we subtract + y = (keyIndex - .5f) * dimension; + } + + var centerIndex = (firstValue + currentValue) / 2.0f; + + if (tileShapeCollection.SortAxis == Axis.X) + { + y = (centerIndex - .5f) * dimension; + height = (currentValue - firstValue + 1) * dimension; + } + else + { + x = (centerIndex + .5f) * dimension; + width = (currentValue - firstValue + 1) * dimension; + } + + AddRectangleStrip(tileShapeCollection, x, y, width, height); + } + + private static void AddRectangleStrip(TileShapeCollection tileShapeCollection, float x, float y, float width, float height) + { + AxisAlignedRectangle rectangle = new AxisAlignedRectangle(); + rectangle.X = x; + rectangle.Y = y; + rectangle.Width = width; + rectangle.Height = height; + + if (tileShapeCollection.Visible) + { + rectangle.Visible = true; + } + + tileShapeCollection.Rectangles.Add(rectangle); + } + + static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + Scene scene, IEnumerable namesToUse) + { + // prob need to clear out the tileShapeCollection + + float dimension = float.NaN; + float dimensionHalf = 0; + + for (int i = 0; i < scene.Sprites.Count; i++) + { + if (namesToUse.Contains(scene.Sprites[i].Name)) + { + + if (float.IsNaN(dimension)) + { + dimension = scene.Sprites[i].Width; + dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + } + + tileShapeCollection.AddCollisionAtWorld(scene.Sprites[i].X, scene.Sprites[i].Y); + + } + + } + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap) + { + + var tilesWithCollision = layeredTileMap.TileProperties + .Where(item => item.Value.Any(property => property.Name == "HasCollision" && (string)property.Value == "True")) + .Select(item => item.Key).ToList(); + + tileShapeCollection.AddCollisionFrom(layeredTileMap, tilesWithCollision); + + } + + } + + +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileEntities/TileEntityInstantiator.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileEntities/TileEntityInstantiator.cs new file mode 100644 index 000000000..7e119e90d --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileEntities/TileEntityInstantiator.cs @@ -0,0 +1,525 @@ +using FlatRedBallWpf.DataTypes; +using FlatRedBall.TileGraphics; +using System; +using System.Collections.Generic; +using System.Linq; +using FlatRedBallWpf.Performance; +using FlatRedBall.Graphics; +using System.Reflection; +using TMXGlueLib.DataTypes; +using System.Collections; +using FlatRedBall.Math.Geometry; + +namespace FlatRedBall.TileEntities +{ + public static class TileEntityInstantiator + { + /// + /// A dictionary that stores all available values for a given type. + /// + /// + /// The structure of this class is a dictionary, where each entry in the dictionary is a list of dictionaries. This mgiht + /// seem confusing so let's look at an example. + /// This was originally created to cache values from CSVs. Let's say there's a type called PlatformerValues. + /// The class PlatformerValues would be the key in the dictionary. + /// Of course, platformer values can be defined in multiple entities (if multiple entities are platformers). + /// Each CSV in each entity becomes 1 dictionary, but since there can be multiple CSVs, there is a list. Therefore, the struture + /// might look like this: + /// * PlatformerValues + /// * List from Entity 1 + /// * OnGround + /// * InAir + /// * List from Entity 2 + /// * OnGround + /// * In Air + /// // and so on... + /// + static Dictionary> allDictionaries = new Dictionary>(); + + /// + /// Creates entities from a single layer for any tile with the EntityToCreate property. + /// + /// The layer to create entities from. + /// The map which contains the mapLayer instance. + public static void CreateEntitiesFrom(MapDrawableBatch mapLayer, LayeredTileMap layeredTileMap) + { + var entitiesToRemove = new List(); + + CreateEntitiesFrom(entitiesToRemove, mapLayer, layeredTileMap.TileProperties); + + foreach (var entityToRemove in entitiesToRemove) + { + string remove = entityToRemove; + mapLayer.RemoveTiles(t => t.Any(item => (item.Name == "EntityToCreate" || item.Name == "Type") && item.Value as string == remove), layeredTileMap.TileProperties); + } + + } + + public static void CreateEntitiesFrom(LayeredTileMap layeredTileMap) + { + var entitiesToRemove = new List(); + + foreach (var layer in layeredTileMap.MapLayers) + { + CreateEntitiesFrom(entitiesToRemove, layer, layeredTileMap.TileProperties); + } + foreach (var entityToRemove in entitiesToRemove) + { + string remove = entityToRemove; + layeredTileMap.RemoveTiles(t => t.Any(item => (item.Name == "EntityToCreate" || item.Name == "Type") && item.Value as string == remove), layeredTileMap.TileProperties); + } + foreach (var shapeCollection in layeredTileMap.ShapeCollections) + { + CreateEntitiesFromCircles(layeredTileMap, shapeCollection); + + CreateEntitiesFromRectangles(layeredTileMap, shapeCollection); + + CreateEntitiesFromPolygons(layeredTileMap, shapeCollection); + } + } + + private static void CreateEntitiesFromCircles(LayeredTileMap layeredTileMap, ShapeCollection shapeCollection) + { + var circles = shapeCollection.Circles; + for (int i = circles.Count - 1; i > -1; i--) + { + var circle = circles[i]; + if (!string.IsNullOrEmpty(circle.Name) && layeredTileMap.ShapeProperties.ContainsKey(circle.Name)) + { + var properties = layeredTileMap.ShapeProperties[circle.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = circle.Name; + ApplyPropertiesTo(entity, properties, circle.Position); + shapeCollection.Circles.Remove(circle); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.Circles.Add(circle); + circle.AttachTo(entity, false); + } + } + } + } + } + + private static void CreateEntitiesFromRectangles(LayeredTileMap layeredTileMap, ShapeCollection shapeCollection) + { + var rectangles = shapeCollection.AxisAlignedRectangles; + for (int i = rectangles.Count - 1; i > -1; i--) + { + var rectangle = rectangles[i]; + if (!string.IsNullOrEmpty(rectangle.Name) && layeredTileMap.ShapeProperties.ContainsKey(rectangle.Name)) + { + var properties = layeredTileMap.ShapeProperties[rectangle.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = rectangle.Name; + ApplyPropertiesTo(entity, properties, rectangle.Position); + shapeCollection.AxisAlignedRectangles.Remove(rectangle); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.AxisAlignedRectangles.Add(rectangle); + rectangle.AttachTo(entity, false); + } + + } + } + } + } + + private static void CreateEntitiesFromPolygons(LayeredTileMap layeredTileMap, Math.Geometry.ShapeCollection shapeCollection) + { + var polygons = shapeCollection.Polygons; + for (int i = polygons.Count - 1; i > -1; i--) + { + var polygon = polygons[i]; + if (!string.IsNullOrEmpty(polygon.Name) && layeredTileMap.ShapeProperties.ContainsKey(polygon.Name)) + { + var properties = layeredTileMap.ShapeProperties[polygon.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = polygon.Name; + ApplyPropertiesTo(entity, properties, polygon.Position); + shapeCollection.Polygons.Remove(polygon); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.Polygons.Add(polygon); + polygon.AttachTo(entity, false); + } + + } + } + } + } + + private static void CreateEntitiesFrom(List entitiesToRemove, MapDrawableBatch layer, Dictionary> propertiesDictionary) + { + var flatRedBallLayer = SpriteManager.Layers.FirstOrDefault(item => item.Batches.Contains(layer)); + + var dictionary = layer.NamedTileOrderedIndexes; + + // layer needs its position updated: + layer.ForceUpdateDependencies(); + + foreach (var propertyList in propertiesDictionary.Values) + { + var property = + propertyList.FirstOrDefault(item2 => item2.Name == "EntityToCreate" || item2.Name == "Type"); + + if (!string.IsNullOrEmpty(property.Name)) + { + var tileName = propertyList.FirstOrDefault(item => item.Name.ToLowerInvariant() == "name").Value as string; + + var entityType = property.Value as string; + + if (!string.IsNullOrEmpty(entityType) && dictionary.ContainsKey(tileName)) + { + IEntityFactory factory = GetFactory(entityType); + + if (factory == null) + { + string message = + $"The factory for entity {entityType} could not be found. To create instances of this entity, " + + "set its 'CreatedByOtherEntities' property to true in Glue."; + throw new Exception(message); + } + else + { + entitiesToRemove.Add(entityType); + var indexList = dictionary[tileName]; + + foreach (var tileIndex in indexList) + { + var entity = factory.CreateNew(flatRedBallLayer) as PositionedObject; + + ApplyPropertiesTo(entity, layer, tileIndex, propertyList); + } + + } + } + } + } + } + + private static void ApplyPropertiesTo(PositionedObject entity, MapDrawableBatch layer, int tileIndex, List propertiesToAssign) + { + int vertexIndex = tileIndex * 4; + var dimension = + (layer.Vertices[vertexIndex + 1].Position - layer.Vertices[vertexIndex].Position).Length(); + + float dimensionHalf = dimension / 2.0f; + + + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(tileIndex, out left, out bottom); + Microsoft.Xna.Framework.Vector3 position = new Microsoft.Xna.Framework.Vector3(left, bottom, 0); + + var bottomRight = layer.Vertices[tileIndex * 4 + 1].Position; + + float xDifference = bottomRight.X - left; + float yDifference = bottomRight.Y - bottom; + + if (yDifference != 0 || xDifference < 0) + { + float angle = (float)System.Math.Atan2(yDifference, xDifference); + + entity.RotationZ = angle; + + } + + position += entity.RotationMatrix.Right * dimensionHalf; + position += entity.RotationMatrix.Up * dimensionHalf; + + position += layer.Position; + + ApplyPropertiesTo(entity, propertiesToAssign, position); + } + + private static void ApplyPropertiesTo(PositionedObject entity, List propertiesToAssign, Microsoft.Xna.Framework.Vector3 position) + { + if (entity != null) + { + entity.Position = position; + } + + var entityType = entity.GetType(); + var lateBinder = Instructions.Reflection.LateBinder.GetInstance(entityType); + + foreach (var property in propertiesToAssign) + { + // If name is EntityToCreate, skip it: + string propertyName = property.Name; + + bool shouldSet = propertyName != "EntityToCreate"; + + if (shouldSet) + { + if (propertyName == "name") + { + propertyName = "Name"; + } + + var valueToSet = property.Value; + + var propertyType = property.Type; + + if (string.IsNullOrEmpty(propertyType)) + { + propertyType = TryGetPropertyType(entityType, propertyName); + } + + valueToSet = SetValueAccordingToType(valueToSet, propertyName, propertyType, entityType); + try + { + lateBinder.SetValue(entity, propertyName, valueToSet); + } + catch (InvalidCastException e) + { + string assignedType = valueToSet.GetType().ToString() ?? "unknown type"; + assignedType = GetFriendlyNameForType(assignedType); + + string expectedType = "unknown type"; + object outValue; + if (lateBinder.TryGetValue(entity, propertyName, out outValue) && outValue != null) + { + expectedType = outValue.GetType().ToString(); + expectedType = GetFriendlyNameForType(expectedType); + } + + // This means that the property exists but is of a different type. + string message = $"Attempted to assign the property {propertyName} " + + $"to a value of type {assignedType} but expected {expectedType}. " + + $"Check the property type in your TMX and make sure it matches the type on the entity."; + throw new Exception(message, e); + } + catch (Exception e) + { + // Since this code indiscriminately tries to set properties, it may set properties which don't + // actually exist. Therefore, we tolerate failures. + } + } + } + } + + private static string TryGetPropertyType(Type entityType, string propertyName) + { + // todo - cache for perf + var property = entityType.GetProperty(propertyName); + + if (property != null) + { + return property?.PropertyType.FullName; + } + else + { + var field = entityType.GetField(propertyName); + return field?.FieldType.FullName; + } + } + + public static void RegisterDictionary(Dictionary data) + { +#if DEBUG + if(data == null) + { + throw new ArgumentNullException("The argument data is null - do you need to call LoadStaticContent on the type containing this dictionary?"); + } +#endif + + var type = typeof(T).FullName; + + if (allDictionaries.ContainsKey(type) == false) + { + allDictionaries.Add(type, new List()); + } + + if (allDictionaries[type].Contains(data) == false) + { + allDictionaries[type].Add(data); + } + } + + private static string GetFriendlyNameForType(string type) + { + switch (type) + { + case "System.String": return "string"; + case "System.Single": return "float"; + + } + return type; + } + + + private static object SetValueAccordingToType(object valueToSet, string valueName, string valueType, Type entityType) + { + if (valueType == "bool") + { + bool boolValue = false; + + if (bool.TryParse((string)valueToSet, out boolValue)) + { + valueToSet = boolValue; + } + } + else if (valueType == "float") + { + float floatValue; + + if (float.TryParse((string)valueToSet, System.Globalization.NumberStyles.Float, System.Globalization.NumberFormatInfo.InvariantInfo, out floatValue)) + { + valueToSet = floatValue; + } + } + else if (valueType == "int") + { + int intValue; + + if (int.TryParse((string)valueToSet, out intValue)) + { + valueToSet = intValue; + } + } + else if (valueName == "CurrentState") + { + // Since it's part of the class, it uses the "+" separator + var enumTypeName = entityType.FullName + "+VariableState"; + var enumType = typesInThisAssembly.FirstOrDefault(item => item.FullName == enumTypeName); + + valueToSet = Enum.Parse(enumType, (string)valueToSet); + } + else if (valueType != null && allDictionaries.ContainsKey(valueType)) + { + var list = allDictionaries[valueType]; + + foreach (var dictionary in list) + { + if (dictionary.Contains(valueToSet)) + { + valueToSet = dictionary[valueToSet]; + break; + } + } + } + // If this has a + in it, then that might mean it's a state. We should try to get the type, and if we find it, stuff + // it in allDictionaries to make future calls faster + else if (valueType != null && valueType.Contains("+")) + { + var stateType = typesInThisAssembly.FirstOrDefault(item => item.FullName == valueType); + + if (stateType != null) + { + Dictionary allValues = new Dictionary(); + + var fields = stateType.GetFields(BindingFlags.Static | BindingFlags.Public); + + foreach (var field in fields) + { + allValues[field.Name] = field.GetValue(null); + } + + // The list has all the dictioanries that contain values. But for states there is only one set of values, so + // we create a list + List list = new List(); + list.Add(allValues); + + allDictionaries[valueType] = list; + + if (allValues.ContainsKey((string)valueToSet)) + { + valueToSet = allValues[(string)valueToSet]; + } + } + + } + return valueToSet; + } + + + private static void AssignCustomPropertyTo(PositionedObject entity, NamedValue property) + { + throw new NotImplementedException(); + } + + + static Type[] typesInThisAssembly; + private static IEntityFactory GetFactory(string entityType) + { + if (typesInThisAssembly == null) + { +#if WINDOWS_8 || UWP + var assembly = typeof(TileEntityInstantiator).GetTypeInfo().Assembly; + typesInThisAssembly = assembly.DefinedTypes.Select(item=>item.AsType()).ToArray(); + +#else + var assembly = Assembly.GetExecutingAssembly(); + typesInThisAssembly = assembly.GetTypes(); +#endif + } + + +#if WINDOWS_8 || UWP + var filteredTypes = + typesInThisAssembly.Where(t => t.GetInterfaces().Contains(typeof(IEntityFactory)) + && t.GetConstructors().Any(c=>c.GetParameters().Count() == 0)); +#else + var filteredTypes = + typesInThisAssembly.Where(t => t.GetInterfaces().Contains(typeof(IEntityFactory)) + && t.GetConstructor(Type.EmptyTypes) != null); +#endif + + var factories = filteredTypes + .Select( + t => + { +#if WINDOWS_8 || UWP + var propertyInfo = t.GetProperty("Self"); +#else + var propertyInfo = t.GetProperty("Self"); +#endif + var value = propertyInfo.GetValue(null, null); + return value as IEntityFactory; + }).ToList(); + + + var factory = factories.FirstOrDefault(item => + { + var type = item.GetType(); + var methodInfo = type.GetMethod("CreateNew", new[] { typeof(Layer), typeof(float), typeof(float) }); + var returntypeString = methodInfo.ReturnType.Name; + + return entityType == returntypeString || entityType.EndsWith("\\" + returntypeString); + }); + return factory; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AbstractMapLayer.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AbstractMapLayer.cs new file mode 100644 index 000000000..42bfacf6d --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AbstractMapLayer.cs @@ -0,0 +1,17 @@ +using System; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ +#if !UWP + [Serializable] +#endif + [XmlInclude(typeof (MapLayer))] + [XmlInclude(typeof (MapImageLayer))] + [XmlInclude(typeof (mapObjectgroup))] + public abstract class AbstractMapLayer + { + [XmlAttribute("name")] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AnimationChainContainer.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AnimationChainContainer.cs new file mode 100644 index 000000000..93f1ecab6 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/AnimationChainContainer.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Graphics.Animation; +using Microsoft.Xna.Framework.Graphics; +using FlatRedBall.Math; + +namespace FlatRedBall.TileGraphics +{ + /// + /// An AnimationChain container that self animates. This can be used + /// as the container for IAnimationChainAnimatables. + /// + public class AnimationChainContainer + { + double mTimeIntoAnimation = 0; + + public float AnimationSpeed + { + get; + set; + } + + public int CurrentFrameIndex + { + get; + set; + } + + public AnimationFrame CurrentFrame + { + get + { + return AnimationChain[CurrentFrameIndex]; + } + } + + public AnimationChain AnimationChain + { + get; + set; + } + + public Texture2D CurrentTexture + { + get + { + return AnimationChain[CurrentFrameIndex].Texture; + } + } + + public AnimationChainContainer(AnimationChain animationChain) + { + this.AnimationChain = animationChain; + AnimationSpeed = 1; + CurrentFrameIndex = 1; + } + + /// + /// Moves the animation forward by the argument time; + /// + /// The amount of time that has passed since last update. + public void Activity(float secondDifference) + { + if (AnimationChain != null) + { + double modifiedTimePassed = TimeManager.SecondDifference * AnimationSpeed; + + mTimeIntoAnimation += modifiedTimePassed; + + mTimeIntoAnimation = MathFunctions.Loop(mTimeIntoAnimation, AnimationChain.TotalLength); + + UpdateFrameBasedOffOfTimeIntoAnimation(); + } + } + + void UpdateFrameBasedOffOfTimeIntoAnimation() + { + double timeIntoAnimation = mTimeIntoAnimation; + + if (timeIntoAnimation < 0) + { + throw new ArgumentException("The timeIntoAnimation argument must be 0 or positive"); + } + else if (AnimationChain != null && AnimationChain.Count > 1) + { + int frameIndex = 0; + + // Don't start the while loop if the animation + // has a length of 0, will result in an infinite loop + if (AnimationChain.TotalLength > 0) + { + while (timeIntoAnimation >= 0) + { + double frameTime = AnimationChain[frameIndex].FrameLength; + if (timeIntoAnimation < frameTime) + { + CurrentFrameIndex = frameIndex; + + break; + } + else + { + timeIntoAnimation -= frameTime; + + frameIndex = (frameIndex + 1) % AnimationChain.Count; + } + } + } + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ExternalTileset.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ExternalTileset.cs new file mode 100644 index 000000000..1da1d590f --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ExternalTileset.cs @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.5448 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Xml.Serialization; +using System.Collections.Generic; +using TMXGlueLib; + +// +// This source code was auto-generated by xsd, Version=2.0.50727.3038. +// + + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +[XmlRoot("tileset")] +public partial class tileset +{ + + private tilesetImage[] imageField; + + private string nameField; + + private int tilewidthField; + + private int tileheightField; + + private int spacingField; + + private int marginField; + [System.Xml.Serialization.XmlElementAttribute("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public List tile = new List(); + //{ + // get + // { + // return this.tileField; + // } + // set + // { + // if (this.tileField != null && this.tileField.Length > 0) + // { + // return; + // } + // else + // { + // this.tileField = value; + // } + // } + //} + + /// + [System.Xml.Serialization.XmlElementAttribute("image", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tilesetImage[] image { + get { + return this.imageField; + } + set { + this.imageField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string name { + get { + return this.nameField; + } + set { + this.nameField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int tilewidth { + get { + return this.tilewidthField; + } + set { + this.tilewidthField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int tileheight { + get { + return this.tileheightField; + } + set { + this.tileheightField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int spacing { + get { + return this.spacingField; + } + set { + this.spacingField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int margin { + get { + return this.marginField; + } + set { + this.marginField = value; + } + } +} + + +[System.Xml.Serialization.XmlRootAttribute(ElementName = "tileset", Namespace = "", IsNullable = false)] +public class ExternalTileSet : Tileset +{ + + +} + + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +public partial class tilesetImage { + + private string sourceField; + + private int widthField; + + private int heightField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string source { + get { + return this.sourceField; + } + set { + this.sourceField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int width { + get { + return this.widthField; + } + set { + this.widthField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int height { + get { + return this.heightField; + } + set { + this.heightField = value; + } + } +} + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] +public partial class NewDataSet { + + private tileset[] itemsField; + + /// + [System.Xml.Serialization.XmlElementAttribute("tileset")] + public tileset[] Items { + get { + return this.itemsField; + } + set { + this.itemsField = value; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMap.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMap.cs new file mode 100644 index 000000000..147dc50b0 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMap.cs @@ -0,0 +1,1031 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Content.Scene; +using FlatRedBall.Content; +using FlatRedBall.IO; +using FlatRedBall.Debugging; +using FlatRedBall.Performance.Measurement; +using System.IO; +using TMXGlueLib.DataTypes; +using TMXGlueLib; +using FlatRedBall.Graphics; +using FlatRedBall.Graphics.Animation; +using FlatRedBall.Math.Geometry; + +namespace FlatRedBall.TileGraphics +{ + + + public partial class LayeredTileMap : PositionedObject, IVisible + { + #region Fields + FlatRedBall.Math.PositionedObjectList mMapLists = new FlatRedBall.Math.PositionedObjectList(); + + float mRenderingScale = 1; + + float mZSplit = 1; + #endregion + + #region Properties + public float? NumberTilesWide { get; private set; } + public float? NumberTilesTall { get; private set; } + public float? WidthPerTile { get; private set; } + public float? HeightPerTile { get; private set; } + public Dictionary> TileProperties + { + get; + private set; + } = new Dictionary>(); + + + public Dictionary> ShapeProperties + { + get; + private set; + } = new Dictionary>(); + + + public float RenderingScale + { + get + { + return mRenderingScale; + } + set + { + mRenderingScale = value; + foreach (var map in mMapLists) + { + map.RenderingScale = value; + } + + } + } + + public float ZSplit + { + get + { + return mZSplit; + } + set + { + for (int i = 0; i < this.mMapLists.Count; i++) + { + mMapLists[i].RelativeZ = mZSplit * i; + } + } + } + + public List ShapeCollections { get; private set; } = new List(); + + + public FlatRedBall.Math.PositionedObjectList MapLayers + { + get + { + return mMapLists; + } + } + + public List Collisions + { get; private set; } = new List(); + + + bool visible = true; + public bool Visible + { + get + { + return visible; + } + set + { + visible = value; + foreach (var item in this.mMapLists) + { + item.Visible = visible; + } + } + } + + /// + /// Returns the width in world units of the entire map. The map may not visibly extend to this width; + /// + public float Width + { + get + { + if (NumberTilesWide.HasValue && WidthPerTile.HasValue) + { + return NumberTilesWide.Value * WidthPerTile.Value; + } + else + { + return 0; + } + } + } + + /// + /// Returns the height in world units of the entire map. The map may not visibly extend to this height; + /// + public float Height + { + get + { + if (NumberTilesTall.HasValue && HeightPerTile.HasValue) + { + return NumberTilesTall.Value * HeightPerTile.Value; + } + else + { + return 0; + } + } + } + + public LayeredTileMapAnimation Animation { get; set; } + + public List MapProperties { get; set; } + + + IVisible IVisible.Parent + { + get + { + return Parent as IVisible; + } + } + + public bool AbsoluteVisible + { + get + { + if (this.Visible) + { + var parentAsIVisible = this.Parent as IVisible; + + if (parentAsIVisible == null || IgnoresParentVisibility) + { + return true; + } + else + { + // this is true, so return if the parent is visible: + return parentAsIVisible.AbsoluteVisible; + } + } + else + { + return false; + } + } + } + + public bool IgnoresParentVisibility + { + get; + set; + } + + + #endregion + + public IEnumerable TileNamesWith(string propertyName) + { + foreach (var item in TileProperties.Values) + { + if (item.Any(item2 => item2.Name == propertyName)) + { + var hasName = item.Any(item2 => item2.Name == "Name"); + + if (hasName) + { + yield return item.First(item2 => item2.Name == "Name").Value as string; + } + } + } + } + + + public static LayeredTileMap FromScene(string fileName, string contentManagerName) + { + return FromScene(fileName, contentManagerName, false); + } + + public static LayeredTileMap FromScene(string fileName, string contentManagerName, bool verifySameTexturePerLayer) + { + Section.GetAndStartContextAndTime("Initial FromScene"); + LayeredTileMap toReturn = new LayeredTileMap(); + + string absoluteFileName = FileManager.MakeAbsolute(fileName); + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("FromFile"); + SceneSave ses = SceneSave.FromFile(absoluteFileName); + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("BreaksNStuff"); + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(absoluteFileName); + + var breaks = GetZBreaks(ses.SpriteList); + + int valueBefore = 0; + + MapDrawableBatch mdb; + int valueAfter; + + float zValue = 0; + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("Create MDBs"); + + for (int i = 0; i < breaks.Count; i++) + { + valueAfter = breaks[i]; + + int count = valueAfter - valueBefore; + + mdb = MapDrawableBatch.FromSpriteSaves(ses.SpriteList, valueBefore, count, contentManagerName, verifySameTexturePerLayer); + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = zValue; + toReturn.mMapLists.Add(mdb); + zValue += toReturn.mZSplit; + valueBefore = valueAfter; + } + + valueAfter = ses.SpriteList.Count; + if (valueBefore != valueAfter) + { + int count = valueAfter - valueBefore; + + mdb = MapDrawableBatch.FromSpriteSaves(ses.SpriteList, valueBefore, count, contentManagerName, verifySameTexturePerLayer); + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = zValue; + + toReturn.mMapLists.Add(mdb); + } + Section.EndContextAndTime(); + FileManager.RelativeDirectory = oldRelativeDirectory; + return toReturn; + } + + public static LayeredTileMap FromReducedTileMapInfo(string fileName, string contentManagerName) + { + using (Stream inputStream = FileManager.GetStreamForFile(fileName)) + using (BinaryReader binaryReader = new BinaryReader(inputStream)) + { + ReducedTileMapInfo rtmi = ReducedTileMapInfo.ReadFrom(binaryReader); + + + + string fullFileName = fileName; + if (FileManager.IsRelative(fullFileName)) + { + fullFileName = FileManager.RelativeDirectory + fileName; + } + + var toReturn = FromReducedTileMapInfo(rtmi, contentManagerName, fileName); + toReturn.Name = fullFileName; + return toReturn; + } + } + + public static LayeredTileMap FromReducedTileMapInfo(TMXGlueLib.DataTypes.ReducedTileMapInfo rtmi, string contentManagerName, string tilbToLoad) + { + var toReturn = new LayeredTileMap(); + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(tilbToLoad); + + MapDrawableBatch mdb; + + if (rtmi.NumberCellsWide != 0) + { + toReturn.NumberTilesWide = rtmi.NumberCellsWide; + } + + if (rtmi.NumberCellsTall != 0) + { + toReturn.NumberTilesTall = rtmi.NumberCellsTall; + } + + toReturn.WidthPerTile = rtmi.QuadWidth; + toReturn.HeightPerTile = rtmi.QuadHeight; + + for (int i = 0; i < rtmi.Layers.Count; i++) + { + var reducedLayer = rtmi.Layers[i]; + + mdb = MapDrawableBatch.FromReducedLayer(reducedLayer, toReturn, rtmi, contentManagerName); + + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = reducedLayer.Z; + toReturn.mMapLists.Add(mdb); + + } + FileManager.RelativeDirectory = oldRelativeDirectory; + + return toReturn; + } + + public static LayeredTileMap FromTiledMapSave(string fileName, string contentManager) + { + TiledMapSave tms = TiledMapSave.FromFile(fileName); + + // Ultimately properties are tied to tiles by the tile name. + // If a tile has no name but it has properties, those properties + // will be lost in the conversion. Therefore, we have to add name properties. + tms.MoveTypeToProperties(); + +#if DEBUG + CheckForDuplicateTilesets(tms); +#endif + + tms.NameUnnamedTilesetTiles(); + tms.NameUnnamedObjects(); + + + string directory = FlatRedBall.IO.FileManager.GetDirectory(fileName); + + var rtmi = ReducedTileMapInfo.FromTiledMapSave( + tms, 1, 0, directory, FileReferenceType.Absolute); + + var toReturn = FromReducedTileMapInfo(rtmi, contentManager, fileName); + + AddShapeCollections(toReturn, tms); + + foreach (var layer in tms.MapLayers) + { + var matchingLayer = toReturn.MapLayers.FirstOrDefault(item => item.Name == layer.Name); + + + if (matchingLayer != null) + { + if (layer is MapLayer) + { + var mapLayer = layer as MapLayer; + foreach (var propertyValues in mapLayer.properties) + { + matchingLayer.Properties.Add(new NamedValue + { + Name = propertyValues.StrippedName, + Value = propertyValues.value, + Type = propertyValues.Type + }); + } + + matchingLayer.Visible = mapLayer.visible == 1; + } + } + } + + + foreach (var tileset in tms.Tilesets) + { + foreach (var tile in tileset.TileDictionary.Values) + { + int propertyCountFromTileset = 0; + + if (tile.properties.Count != 0) + { + // this needs a name: + string name = tile.properties.FirstOrDefault(item => item.StrippedName.ToLowerInvariant() == "name")?.value; + // todo - eventually need to copy default values from the Tileset to the tile here + AddPropertiesToMap(tms, toReturn.TileProperties, tile.properties, null, name); + } + } + } + + foreach (var objectLayer in tms.objectgroup) + { + if (objectLayer.@object != null) + { + foreach (var objectInstance in objectLayer.@object) + { + TMXGlueLib.Tileset tileset = null; + int propertyCountFromTileset = 0; + + var objectProperties = objectInstance.properties; + List tilesetProperties = null; + if (objectInstance.gid != null) + { + tileset = tms.GetTilesetForGid(objectInstance.gid.Value); + if (tileset.TileDictionary.ContainsKey(objectInstance.gid.Value - tileset.Firstgid)) + { + tilesetProperties = tileset.TileDictionary[objectInstance.gid.Value - tileset.Firstgid].properties; + propertyCountFromTileset = tilesetProperties.Count; + } + } + + + if (objectProperties.Count + propertyCountFromTileset != 0) + { + string name = objectInstance.Name; + // if name is null, check the properties: + if (string.IsNullOrEmpty(name)) + { + name = objectProperties.FirstOrDefault(item => item.StrippedNameLower == "name")?.value; + } + + var objectInstanceIsTile = objectInstance.gid != null; + + if (objectInstanceIsTile) + { + AddPropertiesToMap(tms, toReturn.TileProperties, objectProperties, tilesetProperties, name); + } + else + { + AddPropertiesToMap(tms, toReturn.ShapeProperties, objectProperties, tilesetProperties, name); + } + } + } + } + } + + var tmxDirectory = FileManager.GetDirectory(fileName); + + // add image layers + foreach (var imageLayer in tms.ImageLayers) + { + var imageLayerFile = tmxDirectory + imageLayer.ImageObject.Source; + var texture = FlatRedBallServices.Load(imageLayerFile); + + var newSprite = new Sprite + { + Texture = texture, + Width = imageLayer.ImageObject.Width, + Height = imageLayer.ImageObject.Height, + X = imageLayer.ImageObject.Width / 2 + imageLayer.OffsetX, + Y = -imageLayer.ImageObject.Height / 2 + imageLayer.OffsetY + }; + + var mdb = new MapDrawableBatch(1, texture); + mdb.AttachTo(toReturn, false); + mdb.Paste(newSprite); + mdb.Visible = imageLayer.Visible; + + toReturn.mMapLists.Add(mdb); + } + + var animationDictionary = new Dictionary(); + + // add animations + foreach (var tileset in tms.Tilesets) + { + + string tilesetImageFile = tmxDirectory + tileset.Images[0].Source; + + if (tileset.SourceDirectory != ".") + { + tilesetImageFile = tmxDirectory + tileset.SourceDirectory + tileset.Images[0].Source; + } + + var texture = FlatRedBallServices.Load(tilesetImageFile); + + foreach (var tile in tileset.Tiles.Where(item => item.Animation != null && item.Animation.Frames.Count != 0)) + { + var animation = tile.Animation; + + var animationChain = new AnimationChain(); + foreach (var frame in animation.Frames) + { + var animationFrame = new AnimationFrame(); + animationFrame.FrameLength = frame.Duration / 1000.0f; + animationFrame.Texture = texture; + + int tileIdRelative = frame.TileId; + int globalTileId = (int)(tileIdRelative + tileset.Firstgid); + + int leftPixel; + int rightPixel; + int topPixel; + int bottomPixel; + TiledMapSave.GetPixelCoordinatesFromGid((uint)globalTileId, tileset, out leftPixel, out topPixel, out rightPixel, out bottomPixel); + + animationFrame.LeftCoordinate = MapDrawableBatch.CoordinateAdjustment + leftPixel / (float)texture.Width; + animationFrame.RightCoordinate = -MapDrawableBatch.CoordinateAdjustment + rightPixel / (float)texture.Width; + + animationFrame.TopCoordinate = MapDrawableBatch.CoordinateAdjustment + topPixel / (float)texture.Height; + animationFrame.BottomCoordinate = -MapDrawableBatch.CoordinateAdjustment + bottomPixel / (float)texture.Height; + + + animationChain.Add(animationFrame); + } + + var property = tile.properties.FirstOrDefault(item => item.StrippedNameLower == "name"); + + if (property == null) + { + throw new InvalidOperationException( + $"The tile with ID {tile.id} has an animation, but it doesn't have a Name property, which is required for animation."); + } + else + { + animationDictionary.Add(property.value, animationChain); + } + + } + + } + + toReturn.Animation = new LayeredTileMapAnimation(animationDictionary); + + AddTileShapeCollections(toReturn, tms); + + + toReturn.MapProperties = tms.properties + .Select(propertySave => new NamedValue + { Name = propertySave.name, Value = propertySave.value, Type = propertySave.Type }) + .ToList(); + + + return toReturn; + } + + /// + /// Throws an exception if the same tileset is added to the TiledMapSave twice - this can cause duplicate names in unnamed tiles. + /// + private static void CheckForDuplicateTilesets(TiledMapSave tiledMapSave) + { + foreach (var tileset in tiledMapSave.Tilesets) + { + if (!string.IsNullOrEmpty(tileset.Source)) + { + var duplicateExists = tiledMapSave.Tilesets.Any(other => other != tileset && other.Source == tileset.Source); + + if (duplicateExists) + { + throw new Exception($"The tileset {tileset.Source} is referenced in the .tmx file {tiledMapSave.FileName} more than once. This can cause tile name conflicts."); + } + } + } + } + + private static void AddTileShapeCollections(LayeredTileMap layeredTileMap, TiledMapSave tms) + { + var allTilesets = tms.Tilesets; + + Dictionary hasShapesDictionary = new Dictionary(); + + Dictionary nameCollisionPairs = new Dictionary(); + + + for (int i = 0; i < tms.Layers.Count; i++) + { + var layer = tms.Layers[i]; + // Currently we only support 1 tileset per layer, so we'll find the tileset for this layer + var firstNonZero = layer.data[0].tiles.FirstOrDefault(item => item != 0); + + if (firstNonZero != 0) + { + var tileset = tms.GetTilesetForGid(firstNonZero); + + if (tileset != null) + { + bool hasShapes; + if (hasShapesDictionary.ContainsKey(tileset)) + { + hasShapes = hasShapesDictionary[tileset]; + } + else + { + // We don't know if this tileset has shapes yet, so let's figure it out: + hasShapes = tileset.Tiles.Any(item => item.Objects?.@object?.Length > 0); + + hasShapesDictionary[tileset] = hasShapes; + } + + if (hasShapes) + { + AddTileShapeCollectionForLayer(layer, nameCollisionPairs, tileset, tms.tilewidth, i); + + } + } + } + } + foreach (var item in nameCollisionPairs.Values) + { + var sortOnY = layeredTileMap.Height > layeredTileMap.Width; + if (sortOnY) + { + item.SortAxis = Math.Axis.Y; + } + else + { + item.SortAxis = Math.Axis.X; + } + + item.RefreshAllRepositionDirections(); + + layeredTileMap.Collisions.Add(item); + } + } + + private static TileCollisions.TileShapeCollection GetOrAddTileShapeCollection(string name, Dictionary collisionDictionary) + { + if (collisionDictionary.ContainsKey(name)) + { + return collisionDictionary[name]; + } + else + { + var collection = new TileCollisions.TileShapeCollection(); + collection.Name = name; + + collisionDictionary[name] = collection; + + return collection; + } + } + + private static void AddTileShapeCollectionForLayer(TMXGlueLib.MapLayer layer, Dictionary collisionDictionary, TMXGlueLib.Tileset tileset, float tileDimension, float z) + { + Math.Geometry.AxisAlignedRectangle rectangle = null; + Math.Geometry.Polygon polygon = null; + Circle circle; + + var tiles = layer.data[0].tiles; + + + bool sortOnY = layer.height > layer.width; + + + foreach (var tilesetTile in tileset.Tiles.Where(item => item.Objects?.@object?.Length > 0)) + { + var tilesetTileGid = tilesetTile.id + tileset.Firstgid; + foreach (var tilesetObject in tilesetTile.Objects.@object) + { + + TiledMapToShapeCollectionConverter.ConvertTiledObjectToFrbShape(tilesetObject, out polygon, out rectangle, out circle); + if (rectangle != null) + { + TileCollisions.TileShapeCollection collection = null; + rectangle.Z = z; + if (sortOnY) + { + for (int y = 0; y < layer.height; y++) + { + for (int x = 0; x < layer.width; x++) + { + AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + else + { + for (int x = 0; x < layer.width; x++) + { + for (int y = 0; y < layer.height; y++) + { + AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + } + else if (polygon != null) + { + TileCollisions.TileShapeCollection collection = null; + + // For tile polygons we want them to be centered on the tile. + // To do this, we shift all points by its position: + for (int i = 0; i < polygon.Points.Count; i++) + { + var point = polygon.Points[i]; + point.X += polygon.Position.X - tileDimension / 2.0f; + point.Y += polygon.Position.Y + tileDimension / 2.0f; + + polygon.SetPoint(i, point); + + } + + polygon.Z = z; + + if (sortOnY) + { + for (int y = 0; y < layer.height; y++) + { + for (int x = 0; x < layer.width; x++) + { + AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + else + { + for (int x = 0; x < layer.width; x++) + { + for (int y = 0; y < layer.height; y++) + { + AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + + } + else if (circle != null) + { + throw new NotImplementedException("Need to handle circles..."); + } + } + } + + } + + private static void AddPolygonCloneAtXY(MapLayer layer, float tileDimension, Polygon polygon, List tiles, long tilesetTileGid, int x, int y, + Dictionary dictionary, ref TileCollisions.TileShapeCollection collectionForThisName) + { + var i = y * layer.width + x; + + if (tiles[i] == tilesetTileGid) + { + if (collectionForThisName == null) + { + collectionForThisName = GetOrAddTileShapeCollection(polygon.Name ?? layer.Name, dictionary); + } + int xIndex = i % layer.width; + // intentional int division + int yIndex = i / layer.width; + + var cloned = polygon.Clone(); + + cloned.X = (xIndex + .5f) * tileDimension; + cloned.Y = -(yIndex + .5f) * tileDimension; + + collectionForThisName.Polygons.Add(cloned); + } + } + + private static void AddRectangleCloneAtXY(MapLayer layer, float tileDimension, AxisAlignedRectangle rectangle, List tiles, long tilesetTileGid, int x, int y, + Dictionary dictionary, ref TileCollisions.TileShapeCollection collectionForThisName) + { + var i = y * layer.width + x; + + var stripedId = tiles[i] & 0x0fffffff; + + if (stripedId == tilesetTileGid) + { + if (collectionForThisName == null) + { + collectionForThisName = GetOrAddTileShapeCollection(rectangle.Name ?? layer.Name, dictionary); + } + + float xIndex = i % layer.width; + // intentional int division + float yIndex = i / layer.width; + + var cloned = rectangle.Clone(); + + // Offset by .5 since polygons are positioned by their center, tiles by top left + cloned.X = (xIndex + .5f) * tileDimension; + cloned.Y = -(yIndex + .5f) * tileDimension; + + collectionForThisName.Rectangles.Add(cloned); + + } + } + + private static void AddShapeCollections(LayeredTileMap layeredTileMap, TiledMapSave tms) + { + foreach (var mapObjectgroup in tms.objectgroup) + { + int indexInAllLayers = tms.MapLayers.IndexOf(mapObjectgroup); + + var shapeCollection = tms.ToShapeCollection(mapObjectgroup.Name); + if (shapeCollection != null && shapeCollection.IsEmpty == false) + { + // This makes all shapes have the same Z as the index layer, which is useful if instantiating objects, so they're layered properly + shapeCollection.Shift(new Microsoft.Xna.Framework.Vector3(0, 0, indexInAllLayers)); + + shapeCollection.Name = mapObjectgroup.Name; + layeredTileMap.ShapeCollections.Add(shapeCollection); + } + } + } + + private static void AddPropertiesToMap(TiledMapSave tms, Dictionary> dictionaryToAddTo, List objectProperties, List tilesetProperties, string name) + { + if (!string.IsNullOrEmpty(name)) + { + List namedValues = new List(); + foreach (var prop in objectProperties) + { + namedValues.Add(new NamedValue() + { Name = prop.StrippedName, Value = prop.value, Type = prop.Type }); + } + + if (tilesetProperties != null) + { + foreach (var tilesetProperty in tilesetProperties) + { + bool hasAlreadyBeenAdded = objectProperties.Any(item => item.StrippedNameLower == tilesetProperty.StrippedNameLower); + + if (!hasAlreadyBeenAdded) + { + namedValues.Add(new NamedValue() + { Name = tilesetProperty.StrippedName, Value = tilesetProperty.value, Type = tilesetProperty.Type }); + } + + } + } + +#if DEBUG + if (dictionaryToAddTo.Any(item => item.Key == name)) + { + // Assume it was a duplicate tile name, but it may not be + string message = $"The tileset contains more than one tile with the name {name}. Names must be unique in a tileset."; + bool hasDuplicateObject = tms.objectgroup.Any(item => item.@object.Any(objectInstance => objectInstance.Name == name)); + if (hasDuplicateObject) + { + message = $"The tileset contains a tile with the name {name}, but this name is already used in an object layer"; + } + throw new InvalidOperationException(message); + } +#endif + + dictionaryToAddTo.Add(name, namedValues); + + } + } + + + + public void AnimateSelf() + { + if (Animation != null) + { + Animation.Activity(this); + } + } + + public void AddToManagers() + { + AddToManagers(null); + } + + public void AddToManagers(FlatRedBall.Graphics.Layer layer) + { + bool isAlreadyManaged = SpriteManager.ManagedPositionedObjects + .Contains(this); + + // This allows AddToManagers to be called multiple times, so it can be added to multiple layers + if (!isAlreadyManaged) + { + SpriteManager.AddPositionedObject(this); + } + foreach (var item in this.mMapLists) + { + item.AddToManagers(layer); + } + } + + //Write some addtomanagers and remove methods + + static List GetZBreaks(List list) + { + List zBreaks = new List(); + + GetZBreaks(list, zBreaks); + + return zBreaks; + + } + + static void GetZBreaks(List list, List zBreaks) + { + zBreaks.Clear(); + + if (list.Count == 0 || list.Count == 1) + return; + + for (int i = 1; i < list.Count; i++) + { + if (list[i].Z != list[i - 1].Z) + zBreaks.Add(i); + } + } + + public LayeredTileMap Clone() + { + var toReturn = base.Clone(); + + toReturn.mMapLists = new Math.PositionedObjectList(); + + foreach (var item in this.MapLayers) + { + var clonedLayer = item.Clone(); + if (item.Parent == this) + { + clonedLayer.AttachTo(toReturn, false); + } + toReturn.mMapLists.Add(clonedLayer); + } + + toReturn.ShapeCollections = new List(); + foreach (var shapeCollection in this.ShapeCollections) + { + toReturn.ShapeCollections.Add(shapeCollection.Clone()); + } + + return toReturn; + } + + public void RemoveFromManagersOneWay() + { + this.mMapLists.MakeOneWay(); + for (int i = 0; i < mMapLists.Count; i++) + { + SpriteManager.RemoveDrawableBatch(this.mMapLists[i]); + } + + SpriteManager.RemovePositionedObject(this); + + for (int i = 0; i < this.Collisions.Count; i++) + { + this.Collisions[i].RemoveFromManagersOneWay(); + } + + this.mMapLists.MakeTwoWay(); + } + + public void Destroy() + { + // Make it one-way because we want the + // contained objects to persist after a destroy + mMapLists.MakeOneWay(); + + for (int i = 0; i < mMapLists.Count; i++) + { + SpriteManager.RemoveDrawableBatch(mMapLists[i]); + } + + for (int i = 0; i < this.Collisions.Count; i++) + { + this.Collisions[i].RemoveFromManagers(); + } + + for (int i = 0; i < this.ShapeCollections.Count; i++) + { + this.ShapeCollections[i].RemoveFromManagers(); + } + + SpriteManager.RemovePositionedObject(this); + + mMapLists.MakeTwoWay(); + } + } + + + + public static class LayeredTileMapExtensions + { + public static void RemoveTiles(this LayeredTileMap map, + Func, bool> predicate, + Dictionary> Properties) + { + // Force execution now for performance reasons + var filteredInfos = Properties.Values.Where(predicate).ToList(); + + foreach (var layer in map.MapLayers) + { + RemoveTilesFromLayer(filteredInfos, layer); + } + } + + public static void RemoveTiles(this MapDrawableBatch layer, + Func, bool> predicate, + Dictionary> Properties) + { + // Force execution now for performance reasons + var filteredInfos = Properties.Values.Where(predicate).ToList(); + + RemoveTilesFromLayer(filteredInfos, layer); + } + + private static void RemoveTilesFromLayer(List> filteredInfos, MapDrawableBatch layer) + { + List indexes = new List(); + + foreach (var itemThatPasses in filteredInfos) + { + string tileName = itemThatPasses + .FirstOrDefault(item => item.Name.ToLowerInvariant() == "name") + .Value as string; + + + if (layer.NamedTileOrderedIndexes.ContainsKey(tileName)) + { + var intsOnThisLayer = + layer.NamedTileOrderedIndexes[tileName]; + + indexes.AddRange(intsOnThisLayer); + } + } + + + layer.RemoveQuads(indexes); + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMapAnimation.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMapAnimation.cs new file mode 100644 index 000000000..6da25ae1d --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/LayeredTileMapAnimation.cs @@ -0,0 +1,63 @@ +using FlatRedBall.Graphics.Animation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FlatRedBall.TileGraphics +{ + public partial class LayeredTileMapAnimation + { + Dictionary mAnimationChainContainers = new Dictionary(); + + public LayeredTileMapAnimation(Dictionary animationChainAssociations) + { + foreach(var kvp in animationChainAssociations) + { + AnimationChainContainer container = new AnimationChainContainer(kvp.Value); + + mAnimationChainContainers.Add(kvp.Key, container); + + } + } + + public void Activity(LayeredTileMap layeredTileMap) + { + foreach (var kvp in mAnimationChainContainers) + { + AnimationChainContainer container = kvp.Value; + + + int indexBefore = container.CurrentFrameIndex; + container.Activity(TimeManager.SecondDifference); + if (container.CurrentFrameIndex != indexBefore) + { + ReactToChangedAnimationFrame(kvp.Key, kvp.Value, layeredTileMap); + + } + } + } + + + private void ReactToChangedAnimationFrame(string spriteName, AnimationChainContainer animationChainContainer, LayeredTileMap layeredTileMap) + { + AnimationFrame animationFrame = animationChainContainer.CurrentFrame; + + foreach (var mapLayer in layeredTileMap.MapLayers) + { + var nameDictionary = mapLayer.NamedTileOrderedIndexes; + + if (nameDictionary.ContainsKey(spriteName)) + { + var indexes = nameDictionary[spriteName]; + + foreach (int value in indexes) + { + mapLayer.PaintTileTextureCoordinates(value, animationFrame.LeftCoordinate, animationFrame.TopCoordinate, + animationFrame.RightCoordinate, animationFrame.BottomCoordinate); + } + } + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapDrawableBatch.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapDrawableBatch.cs new file mode 100644 index 000000000..c2ecf620f --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapDrawableBatch.cs @@ -0,0 +1,1386 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Graphics; +using Microsoft.Xna.Framework.Graphics; +using FlatRedBall; +using Microsoft.Xna.Framework; +using FlatRedBall.Content; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using FlatRedBall.Input; +using FlatRedBall.Debugging; +using FlatRedBall.Math; +using TMXGlueLib.DataTypes; + +namespace FlatRedBall.TileGraphics +{ + public enum SortAxis + { + None, + X, + Y + } + + public class MapDrawableBatch : PositionedObject, IVisible, IDrawableBatch + { + #region Fields + protected Tileset mTileset; + + #region XML Docs + /// + /// The effect used to draw. Shared by all instances for performance reasons + /// + #endregion + private static BasicEffect mBasicEffect; + private static AlphaTestEffect mAlphaTestEffect; + + /// + /// The vertices used to draw the map. + /// + /// + /// Coordinate order is: + /// 3 2 + /// + /// 0 1 + /// + protected VertexPositionTexture[] mVertices; + protected Texture2D mTexture; + #region XML Docs + /// + /// The indices to draw the shape + /// + #endregion + protected int[] mIndices; + + Dictionary> mNamedTileOrderedIndexes = new Dictionary>(); + + private int mCurrentNumberOfTiles = 0; + + + private SortAxis mSortAxis; + + #endregion + + #region Properties + + public List Properties + { + get; + private set; + } = new List(); + + /// + /// The axis on which tiles are sorted. This is used to perform tile culling for performance. + /// Setting this to SortAxis.None will turn off culling. + /// + public SortAxis SortAxis + { + get + { + return mSortAxis; + } + set + { + mSortAxis = value; + } + } + + #region XML Docs + /// + /// Here we tell the engine if we want this batch + /// updated every frame. Since we have no updating to + /// do though, we will set this to false + /// + #endregion + public bool UpdateEveryFrame + { + get { return true; } + } + + public float RenderingScale + { + get; + set; + } + + public Dictionary> NamedTileOrderedIndexes + { + get + { + return mNamedTileOrderedIndexes; + } + } + + public bool Visible + { + get; + set; + } + + public bool ZBuffered + { + get; + set; + } + + public int QuadCount + { + get + { + return mVertices.Length / 4; + } + } + + public VertexPositionTexture[] Vertices + { + get + { + return mVertices; + } + } + + public Texture2D Texture + { + get + { + return mTexture; + } + set + { + if (value == null) + { + throw new Exception("Texture can't be null."); + } + + if (mTexture != null && (mTexture.Width != value.Width || mTexture.Height != value.Height)) + { + throw new Exception("New texture must match previous texture dimensions."); + } + + mTexture = value; + } + } + + // Doing these properties this way lets me avoid a computational step of 1 - ParallaxMultiplier in the Update() function + // To explain the get & set values, algebra: + // if _parallaxMultiplier = 1 - value (set) + // then _parallaxMultiplier - 1 = -value + // so -(_parallaxMultiplier - 1) = value + // thus -_parallaxMultiplier + 1 = value (get) + private float _parallaxMultiplierX; + public float ParallaxMultiplierX + { + get { return -_parallaxMultiplierX + 1; } + set { _parallaxMultiplierX = 1 - value; } + } + + private float _parallaxMultiplierY; + public float ParallaxMultiplierY + { + get { return -_parallaxMultiplierY + 1; } + set { _parallaxMultiplierY = 1 - value; } + } + + public TextureFilter? TextureFilter { get; set; } = null; + + + #endregion + + #region Constructor / Initialization + + // this exists purely for Clone + public MapDrawableBatch() + { + + } + + public MapDrawableBatch(int numberOfTiles, Texture2D texture) + : base() + { + if (texture == null) + throw new ArgumentNullException("texture"); + + Visible = true; + InternalInitialize(); + + mTexture = texture; + mVertices = new VertexPositionTexture[4 * numberOfTiles]; + mIndices = new int[6 * numberOfTiles]; + } + + #region XML Docs + /// + /// Create and initialize all assets + /// + #endregion + public MapDrawableBatch(int numberOfTiles, int textureTileDimensionWidth, int textureTileDimensionHeight, Texture2D texture) + : base() + { + if (texture == null) + throw new ArgumentNullException("texture"); + + Visible = true; + InternalInitialize(); + + mTexture = texture; + mVertices = new VertexPositionTexture[4 * numberOfTiles]; + mIndices = new int[6 * numberOfTiles]; + + mTileset = new Tileset(texture, textureTileDimensionWidth, textureTileDimensionHeight); + } + + //public MapDrawableBatch(int mapWidth, int mapHeight, float mapTileDimension, int textureTileDimension, string tileSetFilename) + // : base() + //{ + // InternalInitialize(); + + + // mTileset = new Tileset(tileSetFilename, textureTileDimension); + // mMapWidth = mapWidth; + // mMapHeight = mapHeight; + + // int numberOfTiles = mapWidth * mapHeight; + // // the number of vertices is 4 times the number of tiles (each tile gets 4 vertices) + // mVertices = new VertexPositionTexture[4 * numberOfTiles]; + + // // the number of indices is 6 times the number of tiles + // mIndices = new short[6 * numberOfTiles]; + // for(int i = 0; i < mapHeight; i++) + // { + // for (int j = 0; j < mapWidth; j++) + // { + // int currentTile = mapHeight * i + j; + // int currentVertex = currentTile * 4; + // float xOffset = j * mapTileDimension; + // float yOffset = i * mapTileDimension; + // int currentIndex = currentTile * 6; // 6 indices per tile + + // // TEMP + // Vector2[] coords = mTileset.GetTextureCoordinateVectorsOfTextureIndex(new Random().Next()%4); + // // END TEMP + + + // // create vertices + // mVertices[currentVertex + 0] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + 0f, 0f), coords[0]); + // mVertices[currentVertex + 1] = new VertexPositionTexture(new Vector3(xOffset + mapTileDimension, yOffset + 0f, 0f), coords[1]); + // mVertices[currentVertex + 2] = new VertexPositionTexture(new Vector3(xOffset + mapTileDimension, yOffset + mapTileDimension, 0f), coords[2]); + // mVertices[currentVertex + 3] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + mapTileDimension, 0f), coords[3]); + + + // // create indices + // mIndices[currentIndex + 0] = (short)(currentVertex + 0); + // mIndices[currentIndex + 1] = (short)(currentVertex + 1); + // mIndices[currentIndex + 2] = (short)(currentVertex + 2); + // mIndices[currentIndex + 3] = (short)(currentVertex + 0); + // mIndices[currentIndex + 4] = (short)(currentVertex + 2); + // mIndices[currentIndex + 5] = (short)(currentVertex + 3); + + // mCurrentNumberOfTiles++; + // } + // } + // mTexture = FlatRedBallServices.Load(@"content/tiles"); + + + + //} + + void InternalInitialize() + { + // We're going to share these because creating effects is slow... + // But is this okay if we tombstone? + if (mBasicEffect == null) + { + mBasicEffect = new BasicEffect(FlatRedBallServices.GraphicsDevice); + + mBasicEffect.VertexColorEnabled = false; + mBasicEffect.TextureEnabled = true; + } + if (mAlphaTestEffect == null) + { + mAlphaTestEffect = new AlphaTestEffect(FlatRedBallServices.GraphicsDevice); + mAlphaTestEffect.Alpha = 1; + mAlphaTestEffect.VertexColorEnabled = false; + + } + + RenderingScale = 1; + + + } + + #endregion + + #region Methods + + public void AddToManagers() + { + SpriteManager.AddDrawableBatch(this); + //SpriteManager.AddPositionedObject(mMapBatch); + } + + public void AddToManagers(Layer layer) + { + SpriteManager.AddToLayer(this, layer); + } + + public static MapDrawableBatch FromScnx(string sceneFileName, string contentManagerName, bool verifySameTexturePerLayer) + { + // TODO: This line crashes when the path is already absolute! + string absoluteFileName = FileManager.MakeAbsolute(sceneFileName); + + // TODO: The exception doesn't make sense when the file type is wrong. + SceneSave saveInstance = SceneSave.FromFile(absoluteFileName); + + int startingIndex = 0; + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(absoluteFileName); + + // get the list of sprites from our map file + List spriteSaveList = saveInstance.SpriteList; + + // we use the sprites as defined in the scnx file to create and draw the map. + MapDrawableBatch mMapBatch = FromSpriteSaves(spriteSaveList, startingIndex, spriteSaveList.Count, contentManagerName, verifySameTexturePerLayer); + + FileManager.RelativeDirectory = oldRelativeDirectory; + // temp + //mMapBatch = new MapDrawableBatch(32, 32, 32f, 64, @"content/tiles"); + return mMapBatch; + + } + + /* This creates a MapDrawableBatch (MDB) from the list of sprites provided to us by the FlatRedBall (FRB) Scene XML (scnx) file. */ + public static MapDrawableBatch FromSpriteSaves(List spriteSaveList, int startingIndex, int count, string contentManagerName, bool verifySameTexturesPerLayer) + { + +#if DEBUG + if (verifySameTexturesPerLayer) + { + VerifySingleTexture(spriteSaveList, startingIndex, count); + } +#endif + + // We got it! We are going to make some assumptions: + // First we need the texture. We'll assume all Sprites + // use the same texture: + + // TODO: I (Bryan) really HATE this assumption. But it will work for now. + SpriteSave firstSprite = spriteSaveList[startingIndex]; + + // This is the file name of the texture, but the file name is relative to the .scnx location + string textureRelativeToScene = firstSprite.Texture; + // so we load the texture + Texture2D texture = FlatRedBallServices.Load(textureRelativeToScene, contentManagerName); + + if (!MathFunctions.IsPowerOfTwo(texture.Width) || !MathFunctions.IsPowerOfTwo(texture.Height)) + { + throw new Exception("The dimensions of the texture file " + texture.Name + " are not power of 2!"); + } + + + // Assume all the dimensions of the textures are the same. I.e. all tiles use the same texture width and height. + // This assumption is safe for Iso and Ortho tile maps. + int tileFileDimensionsWidth = 0; + int tileFileDimensionsHeight = 0; + if (spriteSaveList.Count > startingIndex) + { + SpriteSave s = spriteSaveList[startingIndex]; + + // deduce the dimensionality of the tile from the texture coordinates + tileFileDimensionsWidth = (int)System.Math.Round((double)((s.RightTextureCoordinate - s.LeftTextureCoordinate) * texture.Width)); + tileFileDimensionsHeight = (int)System.Math.Round((double)((s.BottomTextureCoordinate - s.TopTextureCoordinate) * texture.Height)); + + } + + + // alas, we create the MDB + MapDrawableBatch mMapBatch = new MapDrawableBatch(count, tileFileDimensionsWidth, tileFileDimensionsHeight, texture); + + int lastIndexExclusive = startingIndex + count; + + for (int i = startingIndex; i < lastIndexExclusive; i++) + { + SpriteSave spriteSave = spriteSaveList[i]; + + // We don't want objects within the IDB to have a different Z than the IDB itself + // (if possible) because that makes the IDB behave differently when using sorting vs. + // the zbuffer. + const bool setZTo0 = true; + mMapBatch.Paste(spriteSave, setZTo0); + + } + + + + return mMapBatch; + } + + public MapDrawableBatch Clone() + { + return base.Clone(); + } + + // Bring the texture coordinates in to adjust for rendering issues on dx9/ogl + public const float CoordinateAdjustment = .00002f; + + internal static MapDrawableBatch FromReducedLayer(TMXGlueLib.DataTypes.ReducedLayerInfo reducedLayerInfo, LayeredTileMap owner, TMXGlueLib.DataTypes.ReducedTileMapInfo rtmi, string contentManagerName) + { + int tileDimensionWidth = reducedLayerInfo.TileWidth; + int tileDimensionHeight = reducedLayerInfo.TileHeight; + float quadWidth = reducedLayerInfo.TileWidth; + float quadHeight = reducedLayerInfo.TileHeight; + + string textureName = reducedLayerInfo.Texture; + + +#if IOS || ANDROID + + textureName = textureName.ToLowerInvariant(); + +#endif + + Texture2D texture = FlatRedBallServices.Load(textureName, contentManagerName); + + MapDrawableBatch toReturn = new MapDrawableBatch(reducedLayerInfo.Quads.Count, tileDimensionWidth, tileDimensionHeight, texture); + + toReturn.Name = reducedLayerInfo.Name; + + Vector3 position = new Vector3(); + Vector2 tileDimensions = new Vector2(quadWidth, quadHeight); + + + IEnumerable quads = null; + + if (rtmi.NumberCellsWide > rtmi.NumberCellsTall) + { + quads = reducedLayerInfo.Quads.OrderBy(item => item.LeftQuadCoordinate).ToList(); + toReturn.mSortAxis = SortAxis.X; + } + else + { + quads = reducedLayerInfo.Quads.OrderBy(item => item.BottomQuadCoordinate).ToList(); + toReturn.mSortAxis = SortAxis.Y; + } + + foreach (var quad in quads) + { + position.X = quad.LeftQuadCoordinate; + position.Y = quad.BottomQuadCoordinate; + + // The Z of the quad should be relative to this layer, not absolute Z values. + // A multi-layer map will offset the individual layer Z values, the quads should have a Z of 0. + // position.Z = reducedLayerInfo.Z; + + + var textureValues = new Vector4(); + + // The purpose of CoordinateAdjustment is to bring the texture values "in", to reduce the chance of adjacent + // tiles drawing on a given tile quad. If we don't do this, we can get slivers of adjacent colors appearing, causing + // lines or grid patterns. + // To bring the values "in" we have to consider rotated quads. + textureValues.X = CoordinateAdjustment + (float)quad.LeftTexturePixel / (float)texture.Width; // Left + textureValues.Y = -CoordinateAdjustment + (float)(quad.LeftTexturePixel + tileDimensionWidth) / (float)texture.Width; // Right + textureValues.Z = CoordinateAdjustment + (float)quad.TopTexturePixel / (float)texture.Height; // Top + textureValues.W = -CoordinateAdjustment + (float)(quad.TopTexturePixel + tileDimensionHeight) / (float)texture.Height; // Bottom + + // pad before doing any rotations/flipping + const bool pad = true; + if (pad) + { + const float amountToAdd = .0000001f; + textureValues.X += amountToAdd; // Left + textureValues.Y -= amountToAdd; // Right + textureValues.Z += amountToAdd; // Top + textureValues.W -= amountToAdd; // Bottom + } + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedHorizontallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedHorizontallyFlag) + { + var temp = textureValues.Y; + textureValues.Y = textureValues.X; + textureValues.X = temp; + } + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedVerticallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedVerticallyFlag) + { + var temp = textureValues.Z; + textureValues.Z = textureValues.W; + textureValues.W = temp; + } + + int tileIndex = toReturn.AddTile(position, tileDimensions, + //quad.LeftTexturePixel, quad.TopTexturePixel, quad.LeftTexturePixel + tileDimensionWidth, quad.TopTexturePixel + tileDimensionHeight); + textureValues); + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedDiagonallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedDiagonallyFlag) + { + toReturn.ApplyDiagonalFlip(tileIndex); + } + + // This was moved to outside of this conversion, to support shaps + //if (quad.QuadSpecificProperties != null) + //{ + // var listToAdd = quad.QuadSpecificProperties.ToList(); + // listToAdd.Add(new NamedValue { Name = "Name", Value = quad.Name }); + // owner.Properties.Add(quad.Name, listToAdd); + //} + if (quad.RotationDegrees != 0) + { + // Tiled rotates clockwise :( + var rotationRadians = -MathHelper.ToRadians(quad.RotationDegrees); + + Vector3 bottomLeftPos = toReturn.Vertices[tileIndex * 4].Position; + + Vector3 vertPos = toReturn.Vertices[tileIndex * 4 + 1].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 1].Position = vertPos; + + vertPos = toReturn.Vertices[tileIndex * 4 + 2].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 2].Position = vertPos; + + vertPos = toReturn.Vertices[tileIndex * 4 + 3].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 3].Position = vertPos; + + } + + toReturn.RegisterName(quad.Name, tileIndex); + } + + return toReturn; + } + + public void Paste(Sprite sprite) + { + Paste(sprite, false); + } + + public int Paste(Sprite sprite, bool setZTo0) + { + // here we have the Sprite's X and Y in absolute coords as well as its texture coords + // NOTE: I appended the Z coordinate for the sake of iso maps. This SHOULDN'T have an effect on the ortho maps since I believe the + // TMX->SCNX tool sets all z to zero. + + // The AddTile method expects the bottom-left corner + float x = sprite.X - sprite.ScaleX; + float y = sprite.Y - sprite.ScaleY; + float z = sprite.Z; + + if (setZTo0) + { + z = 0; + } + + float width = 2f * sprite.ScaleX; // w + float height = 2f * sprite.ScaleY; // z + + float topTextureCoordinate = sprite.TopTextureCoordinate; + float bottomTextureCoordinate = sprite.BottomTextureCoordinate; + float leftTextureCoordinate = sprite.LeftTextureCoordinate; + float rightTextureCoordinate = sprite.RightTextureCoordinate; + + int tileIndex = mCurrentNumberOfTiles; + + RegisterName(sprite.Name, tileIndex); + + // add the textured tile to our map so that we may draw it. + return AddTile(new Vector3(x, y, z), + new Vector2(width, height), + new Vector4(leftTextureCoordinate, rightTextureCoordinate, topTextureCoordinate, bottomTextureCoordinate)); + + } + + public void Paste(SpriteSave spriteSave) + { + Paste(spriteSave, false); + } + + public int Paste(SpriteSave spriteSave, bool setZTo0) + { + // here we have the Sprite's X and Y in absolute coords as well as its texture coords + // NOTE: I appended the Z coordinate for the sake of iso maps. This SHOULDN'T have an effect on the ortho maps since I believe the + // TMX->SCNX tool sets all z to zero. + + // The AddTile method expects the bottom-left corner + float x = spriteSave.X - spriteSave.ScaleX; + float y = spriteSave.Y - spriteSave.ScaleY; + float z = spriteSave.Z; + if (setZTo0) + { + z = 0; + } + + float width = 2f * spriteSave.ScaleX; // w + float height = 2f * spriteSave.ScaleY; // z + + float topTextureCoordinate = spriteSave.TopTextureCoordinate; + float bottomTextureCoordinate = spriteSave.BottomTextureCoordinate; + float leftTextureCoordinate = spriteSave.LeftTextureCoordinate; + float rightTextureCoordinate = spriteSave.RightTextureCoordinate; + + int tileIndex = mCurrentNumberOfTiles; + + RegisterName(spriteSave.Name, tileIndex); + + // add the textured tile to our map so that we may draw it. + return AddTile(new Vector3(x, y, z), new Vector2(width, height), new Vector4(leftTextureCoordinate, rightTextureCoordinate, topTextureCoordinate, bottomTextureCoordinate)); + } + + private static void VerifySingleTexture(List spriteSaveList, int startingIndex, int count) + { + // Every Sprite should either have the same texture + if (spriteSaveList.Count != 0) + { + string texture = spriteSaveList[startingIndex].Texture; + + for (int i = startingIndex + 1; i < startingIndex + count; i++) + { + SpriteSave ss = spriteSaveList[i]; + + if (ss.Texture != texture) + { + float leftOfSprite = ss.X - ss.ScaleX; + float indexX = leftOfSprite / (ss.ScaleX * 2); + + float topOfSprite = ss.Y + ss.ScaleY; + float indexY = (0 - topOfSprite) / (ss.ScaleY * 2); + + throw new Exception("All Sprites do not have the same texture"); + } + } + + } + } + + private void RegisterName(string name, int tileIndex) + { + int throwaway; + if (!string.IsNullOrEmpty(name) && !int.TryParse(name, out throwaway)) + { + // TEMPORARY: + // The tmx converter + // names all Sprites with + // a number if their name is + // not explicitly set. Therefore + // we have to ignore those and look + // for explicit names (names not numbers). + // Will talk to Domenic about this to fix it. + if (!mNamedTileOrderedIndexes.ContainsKey(name)) + { + mNamedTileOrderedIndexes.Add(name, new List()); + } + + mNamedTileOrderedIndexes[name].Add(tileIndex); + } + } + + Vector2[] coords = new Vector2[4]; + + /// + /// Paints a texture on a tile. This method takes the index of the Sprite in the order it was added + /// to the MapDrawableBatch, so it supports any configuration including non-rectangular maps and maps with + /// gaps. + /// + /// The index of the tile to paint - this matches the index of the tile as it was added. + /// + public void PaintTile(int orderedTileIndex, int newTextureId) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Reusing the coords array saves us on allocation + mTileset.GetTextureCoordinateVectorsOfTextureIndex(newTextureId, coords); + + // Coords are + // 3 2 + // + // 0 1 + + mVertices[currentVertex + 0].TextureCoordinate = coords[0]; + mVertices[currentVertex + 1].TextureCoordinate = coords[1]; + mVertices[currentVertex + 2].TextureCoordinate = coords[2]; + mVertices[currentVertex + 3].TextureCoordinate = coords[3]; + + } + + /// + /// Sets the left and top texture coordiantes of the tile represented by orderedTileIndex. The right and bottom texture coordaintes + /// are set automatically according to the tileset dimensions. + /// + /// The ordered tile index. + /// The left texture coordiante (in UV coordinates) + /// The top texture coordainte (in UV coordinates) + public void PaintTileTextureCoordinates(int orderedTileIndex, float textureXCoordinate, float textureYCoordinate) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + mTileset.GetCoordinatesForTile(coords, textureXCoordinate, textureYCoordinate); + + mVertices[currentVertex + 0].TextureCoordinate = coords[0]; + mVertices[currentVertex + 1].TextureCoordinate = coords[1]; + mVertices[currentVertex + 2].TextureCoordinate = coords[2]; + mVertices[currentVertex + 3].TextureCoordinate = coords[3]; + } + + public void PaintTileTextureCoordinates(int orderedTileIndex, float leftCoordinate, float topCoordinate, float rightCoordinate, float bottomCoordinate) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + mVertices[currentVertex + 0].TextureCoordinate.X = leftCoordinate; + mVertices[currentVertex + 0].TextureCoordinate.Y = bottomCoordinate; + + mVertices[currentVertex + 1].TextureCoordinate.X = rightCoordinate; + mVertices[currentVertex + 1].TextureCoordinate.Y = bottomCoordinate; + + mVertices[currentVertex + 2].TextureCoordinate.X = rightCoordinate; + mVertices[currentVertex + 2].TextureCoordinate.Y = topCoordinate; + + mVertices[currentVertex + 3].TextureCoordinate.X = leftCoordinate; + mVertices[currentVertex + 3].TextureCoordinate.Y = topCoordinate; + } + + + + + // Swaps the top-right for the bottom-left verts + public void ApplyDiagonalFlip(int orderedTileIndex) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + var old0 = mVertices[currentVertex + 0].TextureCoordinate; + + mVertices[currentVertex + 0].TextureCoordinate = mVertices[currentVertex + 2].TextureCoordinate; + mVertices[currentVertex + 2].TextureCoordinate = old0; + } + + public void RotateTextureCoordinatesCounterclockwise(int orderedTileIndex) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + var old3 = mVertices[currentVertex + 3].TextureCoordinate; + + mVertices[currentVertex + 3].TextureCoordinate = mVertices[currentVertex + 2].TextureCoordinate; + mVertices[currentVertex + 2].TextureCoordinate = mVertices[currentVertex + 1].TextureCoordinate; + mVertices[currentVertex + 1].TextureCoordinate = mVertices[currentVertex + 0].TextureCoordinate; + mVertices[currentVertex + 0].TextureCoordinate = old3; + + } + + public void GetTextureCoordiantesForOrderedTile(int orderedTileIndex, out float textureX, out float textureY) + { + // The order is: + // 3 2 + // + // 0 1 + + // So we want to add 3 to the index to get the top-left vert, then use + // the texture coordinates there to get the + Vector2 vector = mVertices[(orderedTileIndex * 4) + 3].TextureCoordinate; + + textureX = vector.X; + textureY = vector.Y; + } + + public void GetBottomLeftWorldCoordinateForOrderedTile(int orderedTileIndex, out float x, out float y) + { + // The order is: + // 3 2 + // + // 0 1 + + // So we just need to mutiply by 4 and not add anything + Vector3 vector = mVertices[(orderedTileIndex * 4)].Position; + + x = vector.X; + y = vector.Y; + } + + /// + /// Adds a tile to the tile map + /// + /// + /// + /// + /// 4 points defining the boundaries in the texture for the tile. + /// (X = left, Y = right, Z = top, W = bottom) + /// + public int AddTile(Vector3 bottomLeftPosition, Vector2 dimensions, Vector4 texture) + { + int toReturn = mCurrentNumberOfTiles; + int currentVertex = mCurrentNumberOfTiles * 4; + + int currentIndex = mCurrentNumberOfTiles * 6; // 6 indices per tile (there are mVertices.Length/4 tiles) + + float xOffset = bottomLeftPosition.X; + float yOffset = bottomLeftPosition.Y; + float zOffset = bottomLeftPosition.Z; + + float width = dimensions.X; + float height = dimensions.Y; + + + // create vertices + mVertices[currentVertex + 0] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + 0f, zOffset), new Vector2(texture.X, texture.W)); + mVertices[currentVertex + 1] = new VertexPositionTexture(new Vector3(xOffset + width, yOffset + 0f, zOffset), new Vector2(texture.Y, texture.W)); + mVertices[currentVertex + 2] = new VertexPositionTexture(new Vector3(xOffset + width, yOffset + height, zOffset), new Vector2(texture.Y, texture.Z)); + mVertices[currentVertex + 3] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + height, zOffset), new Vector2(texture.X, texture.Z)); + + // create indices + mIndices[currentIndex + 0] = currentVertex + 0; + mIndices[currentIndex + 1] = currentVertex + 1; + mIndices[currentIndex + 2] = currentVertex + 2; + mIndices[currentIndex + 3] = currentVertex + 0; + mIndices[currentIndex + 4] = currentVertex + 2; + mIndices[currentIndex + 5] = currentVertex + 3; + + mCurrentNumberOfTiles++; + + return toReturn; + } + + /// + /// Add a tile to the map + /// + /// + /// + /// Top left X coordinate in the core texture + /// Top left Y coordinate in the core texture + /// Bottom right X coordinate in the core texture + /// Bottom right Y coordinate in the core texture + public int AddTile(Vector3 bottomLeftPosition, Vector2 tileDimensions, int textureTopLeftX, int textureTopLeftY, int textureBottomRightX, int textureBottomRightY) + { + // Form vector4 for AddTile overload + var textureValues = new Vector4(); + textureValues.X = (float)textureTopLeftX / (float)mTexture.Width; // Left + textureValues.Y = (float)textureBottomRightX / (float)mTexture.Width; // Right + textureValues.Z = (float)textureTopLeftY / (float)mTexture.Height; // Top + textureValues.W = (float)textureBottomRightY / (float)mTexture.Height; // Bottom + + return AddTile(bottomLeftPosition, tileDimensions, textureValues); + } + + /// + /// Renders the MapDrawableBatch + /// + /// The currently drawing camera + public void Draw(Camera camera) + { + ////////////////////Early Out/////////////////// + + if (!AbsoluteVisible) + { + return; + } + if (mVertices.Length == 0) + { + return; + } + + //////////////////End Early Out///////////////// + + + + int firstVertIndex; + int lastVertIndex; + int indexStart; + int numberOfTriangles; + GetRenderingIndexValues(camera, out firstVertIndex, out lastVertIndex, out indexStart, out numberOfTriangles); + + if (numberOfTriangles != 0) + { + TextureFilter? oldTextureFilter = null; + + if (this.TextureFilter != null && this.TextureFilter != FlatRedBallServices.GraphicsOptions.TextureFilter) + { + oldTextureFilter = FlatRedBallServices.GraphicsOptions.TextureFilter; + FlatRedBallServices.GraphicsOptions.TextureFilter = this.TextureFilter.Value; + } + TextureAddressMode oldTextureAddressMode; + Effect effectTouse = PrepareRenderingStates(camera, out oldTextureAddressMode); + + foreach (EffectPass pass in effectTouse.CurrentTechnique.Passes) + { + // Start each pass + + pass.Apply(); + + int numberVertsToDraw = lastVertIndex - firstVertIndex; + + // Right now this uses the (slower) DrawUserIndexedPrimitives + // It could use DrawIndexedPrimitives instead for much faster performance, + // but to do that we'd have to keep VB's around and make sure to re-create them + // whenever the graphics device is lost. + FlatRedBallServices.GraphicsDevice.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + mVertices, + firstVertIndex, + numberVertsToDraw, + mIndices, + indexStart, numberOfTriangles); + + } + + Renderer.TextureAddressMode = oldTextureAddressMode; + if (ZBuffered) + { + FlatRedBallServices.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + } + if (oldTextureFilter != null) + { + FlatRedBallServices.GraphicsOptions.TextureFilter = oldTextureFilter.Value; + } + } + } + + private Effect PrepareRenderingStates(Camera camera, out TextureAddressMode oldTextureAddressMode) + { + // Set graphics states + FlatRedBallServices.GraphicsDevice.RasterizerState = RasterizerState.CullNone; + FlatRedBall.Graphics.Renderer.BlendOperation = BlendOperation.Regular; + + Effect effectTouse = null; + + if (ZBuffered) + { + FlatRedBallServices.GraphicsDevice.DepthStencilState = DepthStencilState.Default; + camera.SetDeviceViewAndProjection(mAlphaTestEffect, false); + + mAlphaTestEffect.World = Matrix.CreateScale(RenderingScale) * base.TransformationMatrix; + mAlphaTestEffect.Texture = mTexture; + + effectTouse = mAlphaTestEffect; + } + else + { + camera.SetDeviceViewAndProjection(mBasicEffect, false); + + mBasicEffect.World = Matrix.CreateScale(RenderingScale) * base.TransformationMatrix; + mBasicEffect.Texture = mTexture; + effectTouse = mBasicEffect; + } + + + + // We won't need to use any other kind of texture + // address mode besides clamp, and clamp is required + // on the "Reach" profile when the texture is not power + // of two. Let's set it to clamp here so that we don't crash + // on non-power-of-two textures. + oldTextureAddressMode = Renderer.TextureAddressMode; + Renderer.TextureAddressMode = TextureAddressMode.Clamp; + + + + + return effectTouse; + } + + private void GetRenderingIndexValues(Camera camera, out int firstVertIndex, out int lastVertIndex, out int indexStart, out int numberOfTriangles) + { + + firstVertIndex = 0; + + lastVertIndex = mVertices.Length; + + + float tileWidth = mVertices[1].Position.X - mVertices[0].Position.X; + + if (mSortAxis == SortAxis.X) + { + float minX = camera.AbsoluteLeftXEdgeAt(this.Z); + float maxX = camera.AbsoluteRightXEdgeAt(this.Z); + + minX -= this.X; + maxX -= this.X; + + firstVertIndex = GetFirstAfterX(mVertices, minX - tileWidth); + lastVertIndex = GetFirstAfterX(mVertices, maxX) + 4; + } + else if (mSortAxis == SortAxis.Y) + { + float minY = camera.AbsoluteBottomYEdgeAt(this.Z); + float maxY = camera.AbsoluteTopYEdgeAt(this.Z); + + minY -= this.Y; + maxY -= this.Y; + + firstVertIndex = GetFirstAfterY(mVertices, minY - tileWidth); + lastVertIndex = GetFirstAfterY(mVertices, maxY) + 4; + } + + lastVertIndex = System.Math.Min(lastVertIndex, mVertices.Length); + + indexStart = 0;// (firstVertIndex * 3) / 2; + int indexEndExclusive = ((lastVertIndex - firstVertIndex) * 3) / 2; + + numberOfTriangles = (indexEndExclusive - indexStart) / 3; + } + + public static int GetFirstAfterX(VertexPositionTexture[] list, float xGreaterThan) + { + int min = 0; + int originalMax = list.Length / 4; + int max = list.Length / 4; + + int mid = (max + min) / 2; + + while (min < max) + { + mid = (max + min) / 2; + float midItem = list[mid * 4].Position.X; + + if (midItem > xGreaterThan) + { + // Is this the last one? + // Not sure why this is here, because if we have just 2 items, + // this will always return a value of 1 instead + //if (mid * 4 + 4 >= list.Length) + //{ + // return mid * 4; + //} + + // did we find it? + if (mid > 0 && list[(mid - 1) * 4].Position.X <= xGreaterThan) + { + return mid * 4; + } + else + { + max = mid - 1; + } + } + else if (midItem <= xGreaterThan) + { + if (mid == 0) + { + return mid * 4; + } + else if (mid < originalMax - 1 && list[(mid + 1) * 4].Position.X > xGreaterThan) + { + return (mid + 1) * 4; + } + else + { + min = mid + 1; + } + } + } + if (min == 0) + { + return 0; + } + else + { + return list.Length; + } + } + + public static int GetFirstAfterY(VertexPositionTexture[] list, float yGreaterThan) + { + int min = 0; + int originalMax = list.Length / 4; + int max = list.Length / 4; + + int mid = (max + min) / 2; + + while (min < max) + { + mid = (max + min) / 2; + float midItem = list[mid * 4].Position.Y; + + if (midItem > yGreaterThan) + { + // Is this the last one? + // See comment in GetFirstAfterX + //if (mid * 4 + 4 >= list.Length) + //{ + // return mid * 4; + //} + + // did we find it? + if (mid > 0 && list[(mid - 1) * 4].Position.Y <= yGreaterThan) + { + return mid * 4; + } + else + { + max = mid - 1; + } + } + else if (midItem <= yGreaterThan) + { + if (mid == 0) + { + return mid * 4; + } + else if (mid < originalMax - 1 && list[(mid + 1) * 4].Position.Y > yGreaterThan) + { + return (mid + 1) * 4; + } + else + { + min = mid + 1; + } + } + } + if (min == 0) + { + return 0; + } + else + { + return list.Length; + } + } + #region XML Docs + /// + /// Here we update our batch - but this batch doesn't + /// need to be updated + /// + #endregion + public void Update() + { + float leftView = Camera.Main.AbsoluteLeftXEdgeAt(0); + float topView = Camera.Main.AbsoluteTopYEdgeAt(0); + + float cameraOffsetX = leftView - CameraOriginX; + float cameraOffsetY = topView - CameraOriginY; + + this.RelativeX = cameraOffsetX * _parallaxMultiplierX; + this.RelativeY = cameraOffsetY * _parallaxMultiplierY; + + this.TimedActivity(TimeManager.SecondDifference, TimeManager.SecondDifferenceSquaredDividedByTwo, TimeManager.LastSecondDifference); + + // The MapDrawableBatch may be attached to a LayeredTileMap (the container of all layers) + // If so, the player may move the LayeredTileMap and expect all contained layers to move along + // with it. To allow this, we need to have dependencies updated. We'll do this by simply updating + // dependencies here, although I don't know at this point if there's a better way - like if we should + // be adding this to the SpriteManager's PositionedObjectList. This is an improvement so we'll do it for + // now and revisit this in case there's a problem in the future. + this.UpdateDependencies(TimeManager.CurrentTime); + } + + // TODO: I would like to somehow make this a property on the LayeredTileMap, but right now it is easier to put them here + public float CameraOriginY { get; set; } + public float CameraOriginX { get; set; } + + IVisible IVisible.Parent + { + get + { + return this.Parent as IVisible; + } + } + + public bool AbsoluteVisible + { + get + { + if (this.Visible) + { + var parentAsIVisible = this.Parent as IVisible; + + if (parentAsIVisible == null || IgnoresParentVisibility) + { + return true; + } + else + { + // this is true, so return if the parent is visible: + return parentAsIVisible.AbsoluteVisible; + } + } + else + { + return false; + } + } + } + + public bool IgnoresParentVisibility + { + get; + set; + } + + #region XML Docs + /// + /// Don't call this, instead call SpriteManager.RemoveDrawableBatch + /// + #endregion + public void Destroy() + { + this.RemoveSelfFromListsBelongingTo(); + } + + + public void MergeOntoThis(IEnumerable mapDrawableBatches) + { + int quadsToAdd = 0; + int quadsOnThis = QuadCount; + foreach (var mdb in mapDrawableBatches) + { + quadsToAdd += mdb.QuadCount; + } + + + int totalNumberOfVerts = 4 * (this.QuadCount + quadsToAdd); + int totalNumberOfIndexes = 6 * (this.QuadCount + quadsToAdd); + + var oldVerts = mVertices; + var oldIndexes = mIndices; + + mVertices = new VertexPositionTexture[totalNumberOfVerts]; + mIndices = new int[totalNumberOfIndexes]; + + oldVerts.CopyTo(mVertices, 0); + oldIndexes.CopyTo(mIndices, 0); + + int currentQuadIndex = quadsOnThis; + + + int index = 0; + foreach (var mdb in mapDrawableBatches) + { + int startVert = currentQuadIndex * 4; + int startIndex = currentQuadIndex * 6; + int numberOfIndices = mdb.mIndices.Length; + int numberOfNewVertices = mdb.mVertices.Length; + + mdb.mVertices.CopyTo(mVertices, startVert); + mdb.mIndices.CopyTo(mIndices, startIndex); + + + for (int i = startIndex; i < startIndex + numberOfIndices; i++) + { + mIndices[i] += startVert; + } + + for (int i = startVert; i < startVert + numberOfNewVertices; i++) + { + mVertices[i].Position.Z += index + 1; + } + + foreach (var kvp in mdb.mNamedTileOrderedIndexes) + { + string key = kvp.Key; + + List toAddTo; + + if (mNamedTileOrderedIndexes.ContainsKey(key)) + { + toAddTo = mNamedTileOrderedIndexes[key]; + } + else + { + toAddTo = new List(); + mNamedTileOrderedIndexes[key] = toAddTo; + } + + foreach (var namedIndex in kvp.Value) + { + toAddTo.Add(namedIndex + currentQuadIndex); + } + } + + + currentQuadIndex += mdb.QuadCount; + index++; + } + } + + + public void RemoveQuads(IEnumerable quadIndexes) + { + var vertList = mVertices.ToList(); + // Reverse - go from biggest to smallest + foreach (var indexToRemove in quadIndexes.Distinct().OrderBy(item => -item)) + { + // and go from biggest to smallest here too + vertList.RemoveAt(indexToRemove * 4 + 3); + vertList.RemoveAt(indexToRemove * 4 + 2); + vertList.RemoveAt(indexToRemove * 4 + 1); + vertList.RemoveAt(indexToRemove * 4 + 0); + } + + mVertices = vertList.ToArray(); + + // The mNamedTileOrderedIndexes is a dictionary that stores which indexes are stored + // with which tiles. For example, the key in the dictionary may be "Lava", in which case + // the value is the indexes of the tiles that use the Lava tile. + // If we do end up removing any quads, then all following quads will shift, so we need to + // adjust the indexes so the naming works correctly + + List orderedInts = quadIndexes.OrderBy(item => item).Distinct().ToList(); + int numberOfRemovals = 0; + foreach (var kvp in mNamedTileOrderedIndexes) + { + var ints = kvp.Value; + + numberOfRemovals = 0; + + for (int i = 0; i < ints.Count; i++) + { + // Nothing left to test, so subtract and move on.... + if (numberOfRemovals == orderedInts.Count) + { + ints[i] -= numberOfRemovals; + } + else if (ints[i] == orderedInts[numberOfRemovals]) + { + ints.Clear(); + break; + } + else if (ints[i] < orderedInts[numberOfRemovals]) + { + ints[i] -= numberOfRemovals; + } + else + { + while (numberOfRemovals < orderedInts.Count && ints[i] > orderedInts[numberOfRemovals]) + { + numberOfRemovals++; + } + if (numberOfRemovals < orderedInts.Count && ints[i] == orderedInts[numberOfRemovals]) + { + ints.Clear(); + break; + } + + ints[i] -= numberOfRemovals; + } + } + } + } + + #endregion + } + + + + + public static class MapDrawableBatchExtensionMethods + { + + + } + + +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapLayer.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapLayer.cs new file mode 100644 index 000000000..ec9d322a2 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapLayer.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ +#if !UWP + [Serializable] +#endif + public partial class MapLayer : AbstractMapLayer + { + #region Fields + + private IDictionary propertyDictionaryField = null; + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + + private mapLayerData[] dataField; + + private string nameField; + + private int widthField; + + private int heightField; + #endregion + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", this.Name); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("data", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = true)] + public mapLayerData[] data + { + get + { + return this.dataField; + } + set + { + this.dataField = value; + if (dataField != null) + { + foreach (mapLayerData layerData in dataField) + { + layerData.length = width * height; + } + } + } + } + + /// + [XmlAttribute("width")] + public int width + { + get + { + return this.widthField; + } + set + { + this.widthField = value; + if (this.data != null) + { + foreach (mapLayerData layerData in data) + { + layerData.length = width * height; + } + } + } + } + + /// + [XmlAttribute("height")] + public int height + { + get + { + return this.heightField; + } + set + { + this.heightField = value; + if (this.data != null) + { + foreach (mapLayerData layerData in data) + { + layerData.length = width * height; + } + } + } + } + + private int? visibleField; + [XmlAttribute("visible")] + public int visible + { + get + { + return this.visibleField.HasValue ? this.visibleField.Value : 1; + } + set + { + this.visibleField = value; + } + } + + [XmlAttribute("opacity")] + public float Opacity + { + get; set; + } + + [XmlIgnore] + public bool IsVisible + { + get + { + return visible != 0; + } + } + + [XmlIgnore] + public TiledMapSave.LayerVisibleBehavior VisibleBehavior = TiledMapSave.LayerVisibleBehavior.Ignore; + + public override string ToString() + { + return Name; + } + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTileset.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTileset.cs new file mode 100644 index 000000000..2852cdb5d --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTileset.cs @@ -0,0 +1,348 @@ +using System.Collections.Generic; +using System.Xml.Serialization; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using FlatRedBall.IO; +using System.IO; + +namespace TMXGlueLib +{ + /// + [XmlType(AnonymousType = true)] +// ReSharper disable InconsistentNaming + [XmlRoot("mapTileset")] + public class Tileset +// ReSharper restore InconsistentNaming + { + private TilesetImage[] _imageField; + + private mapTilesetTileOffset[] _tileOffsetField; + + private string _sourceField; + + [XmlIgnore] + public string SourceDirectory + { + get + { + if (_sourceField != null && _sourceField.Contains("\\")) + { + // add 1 to include the ending directory + return _sourceField.Substring(0, _sourceField.LastIndexOf('\\') + 1); + } + else + { + return "."; + } + } + } + + public static bool ShouldLoadValuesFromSource = true; + + [XmlAttribute("source", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public string Source + { + get + { + return _sourceField; + } + set + { + this._sourceField = value; + + if (ShouldLoadValuesFromSource) + { + LoadValuesFromSource(); + } + } + } + + [XmlIgnore] + public bool IsShared + { + get + { + return !string.IsNullOrEmpty(Source); + } + } + + private void LoadValuesFromSource() + { + if (!string.IsNullOrEmpty(this._sourceField)) + { + _sourceField = _sourceField.Replace("/", "\\"); + + tileset xts = null; + + try + { + + xts = FileManager.XmlDeserialize(_sourceField); + } + catch (FileNotFoundException) + { + string fileAttemptedToLoad = _sourceField; + if (FileManager.IsRelative(_sourceField)) + { + fileAttemptedToLoad = FileManager.RelativeDirectory + _sourceField; + } + + string message = "Could not find the shared tsx file \n" + fileAttemptedToLoad + + "\nIf this is a relative file name, then the loader will use " + + "the FileManager's RelativeDirectory to make the file absolute. Therefore, be sure to set the FileManger's RelativeDirectory to the file represented by " + + "this fileset before setting this property if setting this property manually."; + + + throw new FileNotFoundException(message); + } + + if (xts.image != null) + { + + Images = new TilesetImage[xts.image.Length]; + + Parallel.For(0, xts.image.Length, count => + { + this.Images[count] = new TilesetImage + { + Source = xts.image[count].source, + height = xts.image[count].height != 0 ? xts.image[count].height : xts.tileheight, + width = xts.image[count].width != 0 ? xts.image[count].width : xts.tilewidth + }; + }); + } + this.Name = xts.name; + this.Margin = xts.margin; + this.Spacing = xts.spacing; + this.Tileheight = xts.tileheight; + this.Tilewidth = xts.tilewidth; + this.Tiles = xts.tile; + } + } + + /// + [XmlElement("tileoffset", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 1)] + public mapTilesetTileOffset[] Tileoffset + { + get + { + return this._tileOffsetField; + } + set + { + if (this._tileOffsetField == null || this._tileOffsetField.Length == 0) + { + this._tileOffsetField = value; + } + } + } + + + /// + [XmlElement("image", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 2)] + public TilesetImage[] Images + { + get + { + return this._imageField; + } + set + { + if (this._imageField == null || this._imageField.Length == 0) + { + this._imageField = value; + } + } + } + + + public bool ShouldSerializeImage() + { + return string.IsNullOrEmpty(this.Source); + } + + + [XmlArray("terraintypes", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 3)] + public List terraintypes = new List(); + + public bool ShouldSerializeterraintypes() + { + return string.IsNullOrEmpty(this.Source); + } + + [XmlElement("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 4)] + public List Tiles = new List(); + public bool ShouldSerializeTiles() + { + return string.IsNullOrEmpty(this.Source); + } + //{ + // get + // { + // return this.tileField; + // } + // set + // { + // if (this.tileField != null && this.tileField.Length > 0) + // { + // return; + // } + // else + // { + // this.tileField = value; + // } + // } + //} + + public void RefreshTileDictionary() + { + _tileDictionaryField = null; + } + + + private IDictionary _tileDictionaryField; + + [XmlIgnore] + public IDictionary TileDictionary + { + get + { + lock (this) + { + if (_tileDictionaryField == null) + { + _tileDictionaryField = new ConcurrentDictionary(); + + if (Tiles != null) + { + //Parallel.ForEach(tile, (t) => + // { + // if (t != null && !tileDictionaryField.ContainsKey((uint)t.id + 1)) + // { + // tileDictionaryField.Add((uint)t.id + 1, t); + // } + // }); + + foreach (var t in Tiles) + { + + // November 11, 2017 - why is this "+1"? + // That's confusing. Is it because in the old days + // it was hardcoded to be the ID of the tile including the offset? + // for multiple tilesets the offset won't be 1... + //uint key = (uint)t.id + 1; + uint key = (uint)t.id; + if (!_tileDictionaryField.ContainsKey(key)) + { + _tileDictionaryField.Add(key, t); + } + } + } + + return _tileDictionaryField; + + } + else + { + return _tileDictionaryField; + } + } + + } + } + + + + /// + [XmlAttribute("firstgid")] + public uint Firstgid + { + get; + set; + } + + /// + [XmlAttribute("name")] + public string Name + { + get; + set; + } + + public bool ShouldSerializeName() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("tilewidth")] + public int Tilewidth + { + get; + set; + } + + public bool ShouldSerializeTilewidth() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("tileheight")] + public int Tileheight + { + get; + set; + } + + public bool ShouldSerializeTileheight() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("spacing")] + public int Spacing + { + get; + set; + } + + public bool ShouldSerializeSpacing() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("margin")] + public int Margin + { + get; + set; + } + + public bool ShouldSerializeMargin() + { + return string.IsNullOrEmpty(this.Source); + } + + public override string ToString() + { + string toReturn = this.Name; + + if (!string.IsNullOrEmpty(Source)) + { + string sourceWithoutPath = FileManager.RemovePath(Source); + toReturn += " (" + sourceWithoutPath + ")"; + } + + return toReturn; + } + } + + public class mapTilesetTerrain + { + public string name { get; set; } + public int tile { get; set; } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTilesetTile.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTilesetTile.cs new file mode 100644 index 000000000..42ff1bfde --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/MapTilesetTile.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + + [XmlType(AnonymousType = true)] + public partial class mapTilesetTile + { + private IDictionary propertyDictionaryField = null; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + ForceRebuildPropertyDictionary(); + } + return propertyDictionaryField; + } + } + } + + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + /// + [XmlAttribute()] + public int id + { + get; + set; + } + + [XmlAttribute("type")] + public string Type { get; set; } + + [XmlElement("animation")] + public TileAnimation Animation + { + get; + set; + } + + [XmlElement("objectgroup")] + public mapObjectgroup Objects { get; set; } + + + + public mapTilesetTile() + { + } + + + public override string ToString() + { + string toReturn = id.ToString(); + + if(PropertyDictionary.Count != 0) + { + toReturn += " ("; + + foreach (var kvp in PropertyDictionary) + { + toReturn += "(" + kvp.Key + "," + kvp.Value + ")"; + } + + + toReturn += ")"; + } + return toReturn; + } + + public void ForceRebuildPropertyDictionary() + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/NamedValue.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/NamedValue.cs new file mode 100644 index 000000000..285280059 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/NamedValue.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TMXGlueLib.DataTypes +{ + public struct NamedValue + { + public string Name; + public string Type; + public object Value; + + public static NamedValue Empty = new NamedValue(); + + + public override string ToString() + { + if(string.IsNullOrEmpty(Type)) + { + return $"{Name}={Value}"; + } + else + { + return $"{Type} {Name}={Value}"; + } + + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs new file mode 100644 index 000000000..fb0ac926b --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Content; +using FlatRedBall.Content.Scene; + +namespace TMXGlueLib.DataTypes +{ + public partial class ReducedQuadInfo + { + + + internal static ReducedQuadInfo FromSpriteSave(SpriteSave spriteSave, int textureWidth, int textureHeight) + { + ReducedQuadInfo toReturn = new ReducedQuadInfo(); + toReturn.LeftQuadCoordinate = spriteSave.X - spriteSave.ScaleX; + toReturn.BottomQuadCoordinate = spriteSave.Y - spriteSave.ScaleY; + + + bool isRotated = spriteSave.RotationZ != 0; + if (isRotated) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedDiagonallyFlag); + } + + var leftTextureCoordinate = System.Math.Min(spriteSave.LeftTextureCoordinate, spriteSave.RightTextureCoordinate); + var topTextureCoordinate = System.Math.Min(spriteSave.TopTextureCoordinate, spriteSave.BottomTextureCoordinate); + + if (spriteSave.LeftTextureCoordinate > spriteSave.RightTextureCoordinate) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedHorizontallyFlag); + } + + if (spriteSave.TopTextureCoordinate > spriteSave.BottomTextureCoordinate) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedVerticallyFlag); + } + + toReturn.LeftTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(leftTextureCoordinate * textureWidth); + toReturn.TopTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(topTextureCoordinate * textureHeight); + + toReturn.Name = spriteSave.Name; + + return toReturn; + } + + + } + + public partial class ReducedTileMapInfo + { + public static bool FastCreateFromTmx = false; + + + /// + /// Converts a TiledMapSave to a ReducedTileMapInfo object + /// + /// The TiledMapSave to convert + /// The amount to scale by - default of 1 + /// The zOffset + /// The directory of the file associated with the tiledMapSave, used to find file references. + /// How the files in the .tmx are referenced. + /// + public static ReducedTileMapInfo FromTiledMapSave(TiledMapSave tiledMapSave, float scale, float zOffset, string directory, FileReferenceType referenceType) + { + var toReturn = new ReducedTileMapInfo + { + NumberCellsTall = tiledMapSave.Height, + NumberCellsWide = tiledMapSave.Width + }; + toReturn.CellHeightInPixels = (ushort)tiledMapSave.tileheight; + toReturn.CellWidthInPixels = (ushort)tiledMapSave.tilewidth; + toReturn.QuadHeight = tiledMapSave.tileheight; + toReturn.QuadWidth = tiledMapSave.tilewidth; + + + if (FastCreateFromTmx) + { + CreateFromTiledMapSave(tiledMapSave, directory, referenceType, toReturn); + } + else + { + // slow: + CreateFromSpriteEditorScene(tiledMapSave, scale, zOffset, referenceType, toReturn); + } + + return toReturn; + + + + } + + private static void CreateFromTiledMapSave(TiledMapSave tiledMapSave, string directory, FileReferenceType referenceType, + ReducedTileMapInfo reducedTileMapInfo) + { + ReducedLayerInfo reducedLayerInfo = null; + + for (int i = 0; i < tiledMapSave.MapLayers.Count; i++) + { + var tiledLayer = tiledMapSave.MapLayers[i]; + + string texture = null; + + uint tileIdOfTexture = 0; + Tileset tileSet = null; + uint? firstGid = null; + + if (tiledLayer is MapLayer) + { + var mapLayer = tiledLayer as MapLayer; + + if (mapLayer.data.Length != 0) + { + firstGid = mapLayer.data[0].tiles.FirstOrDefault(item => item != 0); + } + } + else + { + var objectLayer = tiledLayer as mapObjectgroup; + + var firstObjectWithTexture = objectLayer.@object.First(item => item.gid != 0); + + firstGid = firstObjectWithTexture?.gid; + } + + if (firstGid > 0) + { + tileSet = tiledMapSave.GetTilesetForGid(firstGid.Value); + if (tileSet != null) + { + + if (referenceType == FileReferenceType.NoDirectory) + { + texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FlatRedBall.IO.FileManager.RemoveDotDotSlash(directory); + + } + + texture = FlatRedBall.IO.FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + } + + + + int tileWidth = FlatRedBall.Math.MathFunctions.RoundToInt(tiledMapSave.tilewidth); + int tileHeight = FlatRedBall.Math.MathFunctions.RoundToInt(tiledMapSave.tileheight); + + reducedLayerInfo = new ReducedLayerInfo + { + Z = i, + Texture = texture, + Name = tiledLayer.Name, + TileWidth = tileWidth, + TileHeight = tileHeight, + }; + + reducedTileMapInfo.Layers.Add(reducedLayerInfo); + + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + reducedLayerInfo.TextureId = tilesetIndex; + + + // create the quad here: + if (tiledLayer is MapLayer) + { + AddTileLayerTiles(tiledMapSave, reducedLayerInfo, i, tiledLayer, tileSet, tileWidth, tileHeight); + } + + else if (tiledLayer is mapObjectgroup) + { + AddObjectLayerTiles(reducedLayerInfo, tiledLayer, tileSet, firstGid, tileWidth, tileHeight); + } + } + } + } + + static SpriteSave spriteSaveForConversion = new SpriteSave(); + private static void AddTileLayerTiles(TiledMapSave tiledMapSave, ReducedLayerInfo reducedLayerInfo, int i, AbstractMapLayer tiledLayer, Tileset tileSet, int tileWidth, int tileHeight) + { + var asMapLayer = tiledLayer as MapLayer; + var count = asMapLayer.data[0].tiles.Count; + for (int dataId = 0; dataId < count; dataId++) + { + var dataAtIndex = asMapLayer.data[0].tiles[dataId]; + + if (dataAtIndex != 0) + { + + ReducedQuadInfo quad = new DataTypes.ReducedQuadInfo(); + + float tileCenterX; + float tileCenterY; + float tileZ; + + tiledMapSave.CalculateWorldCoordinates(i, dataId, tileWidth, tileHeight, asMapLayer.width, + out tileCenterX, out tileCenterY, out tileZ); + + quad.LeftQuadCoordinate = tileCenterX - tileWidth / 2.0f; + quad.BottomQuadCoordinate = tileCenterY - tileHeight / 2.0f; + + var gid = dataAtIndex; + + //quad.FlipFlags = (byte)((gid & 0xf0000000) >> 28); + + var valueWithoutFlip = gid & 0x0fffffff; + + spriteSaveForConversion.RotationZ = 0; + spriteSaveForConversion.FlipHorizontal = false; + TiledMapSave.SetSpriteTextureCoordinates(gid, spriteSaveForConversion, tileSet, tiledMapSave.orientation); + + + bool isRotated = spriteSaveForConversion.RotationZ != 0; + if (isRotated) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedDiagonallyFlag); + } + + var leftTextureCoordinate = System.Math.Min(spriteSaveForConversion.LeftTextureCoordinate, spriteSaveForConversion.RightTextureCoordinate); + var topTextureCoordinate = System.Math.Min(spriteSaveForConversion.TopTextureCoordinate, spriteSaveForConversion.BottomTextureCoordinate); + + if (spriteSaveForConversion.LeftTextureCoordinate > spriteSaveForConversion.RightTextureCoordinate) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedHorizontallyFlag); + } + + if (spriteSaveForConversion.TopTextureCoordinate > spriteSaveForConversion.BottomTextureCoordinate) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedVerticallyFlag); + } + + quad.LeftTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(leftTextureCoordinate * tileSet.Images[0].width); + quad.TopTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(topTextureCoordinate * tileSet.Images[0].height); + + + if (tileSet.TileDictionary.ContainsKey(valueWithoutFlip - tileSet.Firstgid)) + { + var dictionary = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary; + if (dictionary.ContainsKey("name")) + { + quad.Name = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary["name"]; + } + else if (dictionary.ContainsKey("Name")) + { + quad.Name = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary["Name"]; + } + } + + reducedLayerInfo?.Quads.Add(quad); + } + } + } + + private static void AddObjectLayerTiles(ReducedLayerInfo reducedLayerInfo, AbstractMapLayer tiledLayer, Tileset tileSet, uint? gid, int tileWidth, int tileHeight) + { + var asMapLayer = tiledLayer as mapObjectgroup; + foreach (var objectInstance in asMapLayer.@object) + { + if (objectInstance.gid > 0) + { + ReducedQuadInfo quad = new DataTypes.ReducedQuadInfo(); + + quad.LeftQuadCoordinate = (float)objectInstance.x; + quad.BottomQuadCoordinate = (float)-objectInstance.y; + + quad.RotationDegrees = (float)objectInstance.Rotation; + + quad.FlipFlags = (byte)(gid.Value & 0xf0000000 >> 7); + + var valueWithoutFlip = gid.Value & 0x0fffffff; + + int leftPixelCoord; + int topPixelCoord; + int rightPixelCoord; + int bottomPixelCoord; + TiledMapSave.GetPixelCoordinatesFromGid(gid.Value, tileSet, + out leftPixelCoord, out topPixelCoord, out rightPixelCoord, out bottomPixelCoord); + + quad.LeftTexturePixel = (ushort)Math.Min(leftPixelCoord, rightPixelCoord); + quad.TopTexturePixel = (ushort)Math.Min(topPixelCoord, bottomPixelCoord); + + quad.Name = objectInstance.Name; + if (string.IsNullOrEmpty(quad.Name)) + { + var prop = quad.QuadSpecificProperties.FirstOrDefault(quadProp => quadProp.Name.ToLowerInvariant() == "name"); + quad.Name = (string)prop.Value; + } + + reducedLayerInfo?.Quads.Add(quad); + + } + } + } + + private static void CreateFromSpriteEditorScene(TiledMapSave tiledMapSave, float scale, float zOffset, FileReferenceType referenceType, ReducedTileMapInfo toReturn) + { + var ses = tiledMapSave.ToSceneSave(scale, referenceType); + + // This is not a stable sort! + //ses.SpriteList.Sort((first, second) => first.Z.CompareTo(second.Z)); + ses.SpriteList = ses.SpriteList.OrderBy(item => item.Z).ToList(); + + ReducedLayerInfo reducedLayerInfo = null; + + float z = float.NaN; + + + int textureWidth = 0; + int textureHeight = 0; + + AbstractMapLayer currentLayer = null; + int indexInLayer = 0; + + + foreach (var spriteSave in ses.SpriteList) + { + if (spriteSave.Z != z) + { + indexInLayer = 0; + z = spriteSave.Z; + + + int layerIndex = FlatRedBall.Math.MathFunctions.RoundToInt(z - zOffset); + var abstractMapLayer = tiledMapSave.MapLayers[layerIndex]; + currentLayer = abstractMapLayer; + + reducedLayerInfo = new ReducedLayerInfo + { + Z = spriteSave.Z, + Texture = spriteSave.Texture, + Name = abstractMapLayer.Name, + TileWidth = FlatRedBall.Math.MathFunctions.RoundToInt(spriteSave.ScaleX * 2), + TileHeight = FlatRedBall.Math.MathFunctions.RoundToInt(spriteSave.ScaleY * 2) + }; + + var mapLayer = abstractMapLayer as MapLayer; + // This should have data: + if (mapLayer != null) + { + var idOfTexture = mapLayer.data[0].tiles.FirstOrDefault(item => item != 0); + Tileset tileSet = tiledMapSave.GetTilesetForGid(idOfTexture); + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + + textureWidth = tileSet.Images[0].width; + textureHeight = tileSet.Images[0].height; + + reducedLayerInfo.TextureId = tilesetIndex; + toReturn.Layers.Add(reducedLayerInfo); + } + + + var objectGroup = tiledMapSave.MapLayers[layerIndex] as mapObjectgroup; + + // This code only works based on the assumption that only one tileset will be used in any given object layer's image objects + var mapObjectgroupObject = objectGroup?.@object.FirstOrDefault(o => o.gid != null); + + if (mapObjectgroupObject?.gid != null) + { + var idOfTexture = mapObjectgroupObject.gid.Value; + Tileset tileSet = tiledMapSave.GetTilesetForGid(idOfTexture); + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + + textureWidth = tileSet.Images[0].width; + textureHeight = tileSet.Images[0].height; + reducedLayerInfo.TextureId = tilesetIndex; + toReturn.Layers.Add(reducedLayerInfo); + } + } + + ReducedQuadInfo quad = ReducedQuadInfo.FromSpriteSave(spriteSave, textureWidth, textureHeight); + + if (currentLayer is mapObjectgroup) + { + var asMapObjectGroup = currentLayer as mapObjectgroup; + var objectInstance = asMapObjectGroup.@object[indexInLayer]; + + // skip over any non-sprite objects: + while (objectInstance.gid == null) + { + indexInLayer++; + if (indexInLayer >= asMapObjectGroup.@object.Length) + { + objectInstance = null; + break; + } + else + { + objectInstance = asMapObjectGroup.@object[indexInLayer]; + } + } + + if (objectInstance != null && objectInstance.properties.Count != 0) + { + var nameProperty = objectInstance.properties.FirstOrDefault(item => item.StrippedNameLower == "name"); + if (nameProperty != null) + { + quad.Name = nameProperty.value; + } + else + { + quad.Name = spriteSave.Name; + + bool needsName = string.IsNullOrEmpty(spriteSave.Name); + if (needsName) + { + quad.Name = $"_{currentLayer.Name}runtime{indexInLayer}"; + } + } + + List list = new List(); + + foreach (var property in objectInstance.properties) + { + list.Add( + new NamedValue + { + Name = property.StrippedName, + Value = property.value, + Type = property.Type + } + ); + } + + quad.QuadSpecificProperties = list; + } + } + + reducedLayerInfo?.Quads.Add(quad); + + indexInLayer++; + } + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.cs new file mode 100644 index 000000000..c213b45d6 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/ReducedTileMapInfo.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.IO; +using FlatRedBall.Content; +using FlatRedBall; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + + + +namespace TMXGlueLib.DataTypes +{ + + #region ReducedQuadInfo + + public partial class ReducedQuadInfo + { + public const byte FlippedHorizontallyFlag = 8; + public const byte FlippedVerticallyFlag = 4; + public const byte FlippedDiagonallyFlag = 2; + + public float LeftQuadCoordinate; + public float BottomQuadCoordinate; + + public ushort LeftTexturePixel; + public ushort TopTexturePixel; + + public byte FlipFlags; + + public string Name; + + public float RotationDegrees; + + public List QuadSpecificProperties + { + get; + set; + } + + public static ReducedQuadInfo ReadFrom(BinaryReader reader) + { + ReducedQuadInfo toReturn = new ReducedQuadInfo(); + + toReturn.LeftQuadCoordinate = reader.ReadSingle(); + toReturn.BottomQuadCoordinate = reader.ReadSingle(); + toReturn.LeftTexturePixel = reader.ReadUInt16(); + toReturn.TopTexturePixel = reader.ReadUInt16(); + + toReturn.Name = reader.ReadString(); + + toReturn.FlipFlags = reader.ReadByte(); + + return toReturn; + } + + + public void WriteTo(BinaryWriter writer) + { + writer.Write(LeftQuadCoordinate); + writer.Write(BottomQuadCoordinate); + writer.Write(LeftTexturePixel); + writer.Write(TopTexturePixel); + + writer.Write(Name); + + writer.Write(FlipFlags); + } + + + public override string ToString() + { + return Name + " " + LeftQuadCoordinate + " " + BottomQuadCoordinate; + } + } + + #endregion + + #region ReducedLayerInfo + + public class ReducedLayerInfo + { + public string Texture; + public string Name; + + public uint NumberOfQuads; + + public float Z; + + // Each tileset can have different widths and heights. + // Currently we only allow one tileset per layer, so we + // can store that width and height here + public int TileWidth; + public int TileHeight; + + public List Quads = new List(); + + // Version 2: + public int TextureId; + + + public static ReducedLayerInfo ReadFrom(BinaryReader reader, int version) + { + ReducedLayerInfo toReturn = new ReducedLayerInfo(); + + toReturn.Z = reader.ReadSingle(); + + toReturn.Texture = reader.ReadString(); + + toReturn.Name = reader.ReadString(); + + toReturn.NumberOfQuads = reader.ReadUInt32(); + + for(int i = 0; i < toReturn.NumberOfQuads; i++) + { + toReturn.Quads.Add( ReducedQuadInfo.ReadFrom(reader)); + } + + if(version >= 2) + { + toReturn.TextureId = reader.ReadInt32(); + } + + return toReturn; + } + + public void WriteTo(BinaryWriter writer, int version) + { + writer.Write(Z); + + writer.Write(Texture); + + writer.Write(Name); + + NumberOfQuads = (uint)Quads.Count; + writer.Write(Quads.Count); + + for (int i = 0; i < NumberOfQuads; i++) + { + Quads[i].WriteTo(writer); + } + + if (version >= 2) + { + writer.Write(TextureId); + } + } + + public override string ToString() + { + return Texture + " (" + Quads.Count + ")"; + } + } + + #endregion + + #region ReducedTileMapInfo + + + public partial class ReducedTileMapInfo + { + public ushort CellWidthInPixels; + public ushort CellHeightInPixels; + + public float QuadWidth; + public float QuadHeight; + + public uint NumberOfLayers; + + + // Version 0: + // Initial version when versioning was tracked. + // Version 1: + // Added: + // int NumberCellsWide; + // int NumberCellsTall; + public int VersionNumber = 2; + + public int NumberCellsWide; + public int NumberCellsTall; + + public List Layers = new List(); + + public static ReducedTileMapInfo ReadFrom(BinaryReader reader) + { + ReducedTileMapInfo toReturn = new ReducedTileMapInfo(); + + toReturn.VersionNumber = reader.ReadInt32(); + + toReturn.CellWidthInPixels = reader.ReadUInt16(); + toReturn.CellHeightInPixels = reader.ReadUInt16(); + + toReturn.QuadHeight = reader.ReadSingle(); + toReturn.QuadWidth = reader.ReadSingle(); + + toReturn.NumberOfLayers = reader.ReadUInt32(); + + for (int i = 0; i < toReturn.NumberOfLayers; i++) + { + + toReturn.Layers.Add(ReducedLayerInfo.ReadFrom(reader, toReturn.VersionNumber)); + } + + // Version 1: + if(toReturn.VersionNumber > 0) + { + toReturn.NumberCellsWide = reader.ReadInt32(); + toReturn.NumberCellsTall = reader.ReadInt32(); + } + + + return toReturn; + } + + public void WriteTo(BinaryWriter writer) + { + writer.Write(VersionNumber); + + writer.Write(CellWidthInPixels); + writer.Write(CellHeightInPixels); + + writer.Write(QuadHeight); + writer.Write(QuadWidth); + + NumberOfLayers = (uint)Layers.Count; + writer.Write(NumberOfLayers); + + for (int i = 0; i < NumberOfLayers; i++) + { + this.Layers[i].WriteTo(writer, VersionNumber); + } + + // Version 1: + if(VersionNumber > 0) + { + writer.Write(NumberCellsWide); + writer.Write(NumberCellsTall); + } + + } + + public static ReducedTileMapInfo FromFile(string fileName) + { + ReducedTileMapInfo rtmi = null; + using (Stream inputStream = FileManager.GetStreamForFile(fileName)) + using (BinaryReader binaryReader = new BinaryReader(inputStream)) + { + rtmi = ReducedTileMapInfo.ReadFrom(binaryReader); + + } + + return rtmi; + } + public override string ToString() + { + return this.Layers.Count.ToString(CultureInfo.InvariantCulture); + } + + public List GetReferencedFiles() + { + List toReturn = new List(); + + foreach (var item in Layers) + { + toReturn.Add(item.Texture); + + } + + return toReturn; + } + } + + #endregion +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimation.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimation.cs new file mode 100644 index 000000000..b0deab311 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimation.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + public class TileAnimation + { + [XmlElement("frame")] + public List Frames + { + get; + set; + } + + public TileAnimation() + { + Frames = new List(); + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimationFrame.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimationFrame.cs new file mode 100644 index 000000000..008ae23ac --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileAnimationFrame.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + [XmlRoot("frame")] + public class TileAnimationFrame + { + [XmlAttribute("tileid")] + public int TileId + { + get; + set; + } + + [XmlAttribute("duration")] + public int Duration + { + get; + set; + } + + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileNodeNetworkCreator.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileNodeNetworkCreator.cs new file mode 100644 index 000000000..73803e435 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TileNodeNetworkCreator.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FlatRedBall.TileGraphics; +using FlatRedBall.Math; + +namespace FlatRedBall.AI.Pathfinding +{ + public static class TileNodeNetworkCreator + { + public static TileNodeNetwork CreateFrom(LayeredTileMap layeredTileMap, DirectionalType directionalType, + Func, bool> predicate) + { + var numberOfTilesWide = + MathFunctions.RoundToInt(layeredTileMap.Width / layeredTileMap.WidthPerTile.Value); + var numberOfTilesTall = + MathFunctions.RoundToInt(layeredTileMap.Height / layeredTileMap.HeightPerTile.Value); + + var tileWidth = layeredTileMap.WidthPerTile.Value; + + var dimensionHalf = tileWidth / 2.0f; + + TileNodeNetwork nodeNetwork = new TileNodeNetwork( + 0 + dimensionHalf, + -layeredTileMap.Height + tileWidth / 2.0f, + tileWidth, + numberOfTilesWide, + numberOfTilesTall, + directionalType); + + + var properties = layeredTileMap.TileProperties; + + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + foreach (var layer in layeredTileMap.MapLayers) + { + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + + nodeNetwork.AddAndLinkTiledNodeWorld(centerX, centerY); + } + } + } + } + } + + nodeNetwork.Visible = true; + + return nodeNetwork; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Conversion.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Conversion.cs new file mode 100644 index 000000000..2da93e0da --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Conversion.cs @@ -0,0 +1,1392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using System.Threading.Tasks; +using FlatRedBall; +using FlatRedBall.AI.Pathfinding; +using FlatRedBall.Content; +using FlatRedBall.Content.AI.Pathfinding; +using FlatRedBall.Content.Math.Geometry; +using FlatRedBall.Content.Polygon; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using FlatRedBall.Math.Geometry; +using System.Globalization; + +namespace TMXGlueLib +{ + #region FileReferenceType enum + public enum FileReferenceType + { + NoDirectory, + Absolute, + Relative + } + #endregion + + public partial class TiledMapSave + { + #region Enums + + public enum CSVPropertyType { Tile, Layer, Map, Object }; + + enum LessOrGreaterDesired + { + Less, + Greater, + NoChange + } + #endregion + + #region Fields + + public static LayerVisibleBehavior LayerVisibleBehaviorValue = LayerVisibleBehavior.Ignore; + public static int MaxDegreeOfParallelism = 1; + + const string animationColumnName = "EmbeddedAnimation (List)"; + + + private static Tuple _offset = new Tuple(0f, 0f, 0f); + + #endregion + + #region Properties + + public static Tuple Offset + { + get { return _offset; } + set { _offset = value; } + } + + #endregion + + public Scene ToScene(string contentManagerName, float scale) + { + var scene = ToSceneSave(scale); + return scene.ToScene(contentManagerName); + } + + /// + /// The FRB plugin uses the properties dictionary to create objects and assign their values. + /// This moves the Type value to the properties so that it can be used later on to create entities. + /// Technically this may cause problems if there is a custom property called Type, but we'll cross that + /// in the future if it ever becomes a problem. + /// + public void MoveTypeToProperties() + { + foreach (var tileset in this.Tilesets) + { + var tilesWithTypes = tileset.Tiles.Where(item => !string.IsNullOrEmpty(item.Type)); + + foreach (var tile in tilesWithTypes) + { + var dictionaryEntry = tileset.TileDictionary[(uint)tile.id]; + + dictionaryEntry.properties.Add(new property { name = "Type", value = tile.Type }); + } + } + + foreach (var objectLayer in this.objectgroup) + { + if (objectLayer.@object != null) + { + foreach (var item in objectLayer.@object) + { + if (item.gid != null) + { + var tileset = GetTilesetForGid(item.gid.Value); + + // todo - need to modify the TMX loader to support reading the Type from an object. Right now it works + // if the type is on the tile in the tileset, but not on the object. But...I'm tired. That will have to + // be something I add later. + + if (tileset.TileDictionary.ContainsKey(item.gid.Value - tileset.Firstgid)) + { + var properties = tileset.TileDictionary[item.gid.Value - tileset.Firstgid]; + if (!string.IsNullOrEmpty(properties.Type)) + { + + item.properties.Add(new property { name = "Type", Type = "string", value = properties.Type }); + item.PropertyDictionary["Type"] = properties.Type; + } + } + + } + } + } + } + + } + + public void NameUnnamedTilesetTiles() + { + foreach (var tileset in this.Tilesets) + { + foreach (var tileDictionary in tileset.TileDictionary) + { + var propertyList = tileDictionary.Value.properties; + var nameProperty = propertyList.FirstOrDefault(item => item.StrippedNameLower == "name"); + + if (nameProperty == null) + { + // create a new property: + var newNameProperty = new property(); + newNameProperty.name = "Name"; + newNameProperty.value = tileset.Name + tileDictionary.Key + "_autoname"; + + propertyList.Add(newNameProperty); + + tileDictionary.Value.ForceRebuildPropertyDictionary(); + } + } + } + } + + public void NameUnnamedObjects() + { + int index = 0; + foreach (var objectLayer in this.objectgroup) + { + // Seems like this can be null, not sure why... + if (objectLayer.@object != null) + { + + foreach (var objectInstance in objectLayer.@object) + { + bool hasName = string.IsNullOrEmpty(objectInstance.Name) == false; + bool hasNameProperty = objectInstance.properties.Any(item => item.StrippedNameLower == "name"); + + if (!hasName && !hasNameProperty) + { + objectInstance.Name = $"object{index}_autoname"; + objectInstance.properties.Add(new TMXGlueLib.property { name = "name", value = objectInstance.Name }); + index++; + + } + else if (hasName && !hasNameProperty) + { + objectInstance.properties.Add(new TMXGlueLib.property { name = "name", value = objectInstance.Name }); + } + } + } + } + } + + public string ToCSVString(CSVPropertyType type = CSVPropertyType.Tile, string layerName = null) + { + var sb = new StringBuilder(); + IEnumerable columnsAsEnumerable = GetColumnNames(type, layerName); + var columnList = columnsAsEnumerable as IList ?? columnsAsEnumerable.ToList(); + WriteColumnHeader(sb, columnList); + WriteColumnValues(sb, columnList, type, layerName); + + return sb.ToString(); + } + + private void WriteColumnValues(StringBuilder sb, IList columnNames, CSVPropertyType type, string layerName) + { + columnNames = columnNames.Select(item => property.GetStrippedName(item)).ToList(); + + + // TODO: There is probably a good way to refactor this code + switch (type) + { + case CSVPropertyType.Tile: + WriteColumnValuesForTile(sb, columnNames); + break; + case CSVPropertyType.Layer: + + WriteColumnValuesForLayer(sb, columnNames, layerName); + break; + case CSVPropertyType.Map: + WriteValuesFromDictionary(sb, null, PropertyDictionary, columnNames, null); + break; + case CSVPropertyType.Object: + this.objectgroup.Where( + og => + layerName == null || + (((AbstractMapLayer)og).Name != null && ((AbstractMapLayer)og).Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))) + .SelectMany(o => o.@object, (o, c) => new { group = o, obj = c, X = c.x, Y = c.y }) + .Where(o => o.obj.gid != null) + .ToList() + .ForEach(o => WriteValuesFromDictionary(sb, o.group.PropertyDictionary, o.obj.PropertyDictionary, columnNames, null)); + break; + } + } + + private void WriteColumnValuesForLayer(StringBuilder sb, IList columnNames, string layerName) + { + var availableItems = + this.Layers.Where( + l => + layerName == null || + (l.Name != null && l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))).ToList(); + + foreach (var l in availableItems) + { + WriteValuesFromDictionary(sb, null, l.PropertyDictionary, columnNames, null); + } + + } + + private void WriteColumnValuesForTile(StringBuilder sb, IList columnNames) + { + for (int i = 0; i < this.Tilesets.Count; i++) + { + Tileset tileSet = this.Tilesets[i]; + + if (tileSet.Tiles != null) + { + Func predicate = + t => t.PropertyDictionary.Count > 0 || + (t.Animation != null && t.Animation.Frames != null && t.Animation.Frames.Count > 0); + + foreach (mapTilesetTile tile in tileSet.Tiles.Where(predicate)) + { + Dictionary propertyDictionary = new Dictionary(tile.PropertyDictionary); + + bool needsName = propertyDictionary.Count != 0 || + (tile.Animation != null && tile.Animation.Frames != null && tile.Animation.Frames.Count != 0); + + if (needsName && propertyDictionary.Keys.Any(item => property.GetStrippedName(item).ToLowerInvariant() == "name") == false) + { + var globalId = tile.id + tileSet.Firstgid; + // This has properties, but no name, so let's give it a name! + propertyDictionary.Add("Name (required, string)", "Unnamed" + globalId); + } + WriteValuesFromDictionary(sb, null, propertyDictionary, columnNames, tile.Animation, i); + } + } + } + foreach (var objectGroup in this.objectgroup) + { + foreach (var @object in objectGroup.@object) + { + if (@object.gid != null) + { + WriteValuesFromDictionary(sb, null, @object.PropertyDictionary, columnNames, null); + } + } + } + } + + static int numberOfUnnamedTiles = 0; + + private void WriteValuesFromDictionary(StringBuilder sb, IDictionary pDictionary, + IDictionary iDictionary, IEnumerable columnNames, TileAnimation animation, int tilesetIndex = 0) + { + + + ///////////////////// Early out ////////////////////// + + if (tilesetIndex >= Tilesets.Count) + { + return; + } + ////////////////// End early out //////////////////// + uint startGid = Tilesets[tilesetIndex].Firstgid; + + string nameValue = GetNameValue(iDictionary); + + List row = new List(); + row.Add(nameValue); + + int layerIndex = -1; + + + + uint endIdExclusive = uint.MaxValue; + if (tilesetIndex < Tilesets.Count - 1) + { + endIdExclusive = Tilesets[tilesetIndex + 1].Firstgid; + } + + + for (int i = 0; i < Layers.Count; i++) + { + var layer = Layers[i]; + // see if any layers reference this tile: + foreach (var data in layer.data) + { + foreach (var tile in data.tiles) + { + if (tile >= startGid && tile < endIdExclusive) + { + layerIndex = i; + break; + } + } + + if (layerIndex != -1) + { + break; + } + } + + if (layerIndex != -1) + { + break; + } + } + + bool hasAnimation = columnNames.Contains("EmbeddedAnimation"); + + if (hasAnimation) + { + AddAnimationFrameAtIndex(animation, row, 0, layerIndex, tilesetIndex); + } + AppendCustomProperties(pDictionary, iDictionary, columnNames, row, false); + + AppendRowToStringBuilder(sb, row); + + if (animation != null && animation.Frames != null) + { + + for (int i = 1; i < animation.Frames.Count; i++) + { + row = new List(); + row.Add(""); // Name column + + + if (hasAnimation) + { + AddAnimationFrameAtIndex(animation, row, i, layerIndex, tilesetIndex); + } + AppendCustomProperties(pDictionary, iDictionary, columnNames, row, true); + AppendRowToStringBuilder(sb, row); + } + } + } + + private void AddAnimationFrameAtIndex(TileAnimation animation, List row, int animationIndex, int indexOfLayerReferencingTileset, int tilesetIndex) + { + if (animation != null && animation.Frames != null && animation.Frames.Count > animationIndex) + { + // public int TileId + // public int Duration + + var frame = animation.Frames[animationIndex]; + + int leftCoordinate = 0; + int rightCoordinate = 16; + int topCoordinate = 0; + int bottomCoordinate = 16; + + var frameId = (uint)frame.TileId; + // not sure why, but need to add 1: + //frameId++; + // Update - I know why, because the TileId + // is relative to the Tileset. I didn't try + // this with multiple tilesets, and the first + // tileset has a starting ID of 1. + frameId += this.Tilesets[tilesetIndex].Firstgid; + + GetPixelCoordinatesFromGid(frameId, this.Tilesets[tilesetIndex], + out leftCoordinate, out topCoordinate, out rightCoordinate, out bottomCoordinate); + + row.Add(string.Format( + "new FlatRedBall.Content.AnimationChain.AnimationFrameSaveBase(TextureName={0}, " + + "FrameLength={1}, LeftCoordinate={2}, RightCoordinate={3}, TopCoordinate={4}, BottomCoordinate={5})", + indexOfLayerReferencingTileset, + (frame.Duration / 1000.0f).ToString(CultureInfo.InvariantCulture), + leftCoordinate.ToString(CultureInfo.InvariantCulture), + rightCoordinate.ToString(CultureInfo.InvariantCulture), + topCoordinate.ToString(CultureInfo.InvariantCulture), + bottomCoordinate.ToString(CultureInfo.InvariantCulture))); + } + else + { + row.Add(null); + } + } + + private static void AppendRowToStringBuilder(StringBuilder sb, List row) + { + bool isFirst = true; + foreach (var originalValue in row) + { + string value = originalValue; + + if (!isFirst) + { + sb.Append(","); + + } + if (value != null) + { + value = value.Replace("\"", "\"\""); + } + + sb.AppendFormat("\"{0}\"", value); + isFirst = false; + } + sb.AppendLine(); + + } + + private static string GetNameValue(IDictionary iDictionary) + { + string nameValue = null; + + bool doesDictionaryContainNameValue = + iDictionary.Any(p => property.GetStrippedName(p.Key).Equals("name", StringComparison.CurrentCultureIgnoreCase)); + + if (doesDictionaryContainNameValue) + { + nameValue = iDictionary.First(p => property.GetStrippedName(p.Key).Equals("name", StringComparison.CurrentCultureIgnoreCase)).Value; + } + else + { + nameValue = "UnnamedTile" + numberOfUnnamedTiles; + numberOfUnnamedTiles++; + } + return nameValue; + } + + private static void AppendCustomProperties(IDictionary pDictionary, IDictionary iDictionary, IEnumerable columnNames, List row, bool forceEmpty) + { + foreach (string columnName in columnNames) + { + string strippedColumnName = property.GetStrippedName(columnName); + + bool isAnimation = + strippedColumnName.Equals("embeddedanimation", StringComparison.CurrentCultureIgnoreCase); + + bool isCustomProperty = !isAnimation && + !strippedColumnName.Equals("name", StringComparison.CurrentCultureIgnoreCase); + + + + if (isCustomProperty) + { + if (!forceEmpty && iDictionary.Any(p => property.GetStrippedName(p.Key).Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase))) + { + var value = + iDictionary.First(p => property.GetStrippedName(p.Key).Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase)).Value; + + row.Add(value); + } + // Victor Chelaru + // October 12, 2014 + // Not sure what pDictionary + // is, but it looks like it's + // only used for "object" CSVs. + // My first question is - do we need + // to use stripped names here? Also, do + // we even want to support object dictionaries + // in the future? How does this fit in with the + // new "level" pattern. + else if (!forceEmpty && pDictionary != null && pDictionary.Any(p => p.Key.Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase))) + { + var value = + pDictionary.First(p => p.Key.Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase)).Value; + + row.Add(value); + } + else + { + row.Add(null); + } + } + } + } + + private static void WriteColumnHeader(StringBuilder sb, IEnumerable columnNames) + { + sb.Append("Name (required)"); + foreach (string columnName in columnNames) + { + string strippedName = property.GetStrippedName(columnName); + + + bool isName = strippedName.Equals("name", StringComparison.CurrentCultureIgnoreCase); + + if (!isName) + { + // Update August 27, 2012 + // We can't just assume that + // all of the column names are + // going to be capitalized. This + // was likely done to force the Name + // property to be capitalized, which we + // want, but we don't want to do it for everything. + //if (columnName.Length > 1) + //{ + // sb.AppendFormat(",{0}{1}", columnName.Substring(0, 1).ToUpper(), columnName.Substring(1)); + //} + //else + //{ + // sb.AppendFormat(",{0}", columnName.ToUpper()); + //} + sb.Append("," + columnName); + } + } + sb.AppendLine(); + } + + /// + /// Compares the stripped name of properties - removing the type + /// + public class CaseInsensitivePropertyEqualityComparer : IEqualityComparer + { + public bool Equals(string x, string y) + { + + return property.GetStrippedName(x).Equals(property.GetStrippedName(y), StringComparison.CurrentCultureIgnoreCase); + } + + public int GetHashCode(string obj) + { + return property.GetStrippedName(obj).ToLowerInvariant().GetHashCode(); + } + } + + private IEnumerable GetColumnNames(CSVPropertyType type, string layerName) + { + var comparer = new CaseInsensitivePropertyEqualityComparer(); + + var columnNames = new HashSet(); + + switch (type) + { + case CSVPropertyType.Tile: + return GetColumnNamesForTile(comparer); + case CSVPropertyType.Layer: + return + this.Layers.Where( + l => + layerName == null || + (l.Name != null && l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))) + .SelectMany(l => l.PropertyDictionary) + .Select(d => d.Key) + .Distinct(comparer); + case CSVPropertyType.Map: + return this.PropertyDictionary.Select(d => d.Key).Distinct(comparer); + case CSVPropertyType.Object: + + List toReturn = new List(); + + toReturn.Add("X (int)"); + toReturn.Add("Y (int)"); + + if (objectgroup != null) + { + + var query1 = + objectgroup.Where(l => + layerName == null || + (l.Name != null && + l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))); + var query2 = + objectgroup.Where(l => + layerName == null || + (l.Name != null && + l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))); + return toReturn + .Union(query1 + .SelectMany(o => o.@object) + .Where(o => o.gid != null) //November 2015 by Jesse Crafts-Finch: will ignore objects which are to be treated as sprites (they have a gid). + .SelectMany(o => o.PropertyDictionary) + .Select(d => d.Key), comparer) + .Union(query2 + .SelectMany(o => o.PropertyDictionary) + .Select(d => d.Key), comparer); + } + else + { + return toReturn; + } + + } + return columnNames; + } + + private IEnumerable GetColumnNamesForTile(CaseInsensitivePropertyEqualityComparer comparer) + { + List toReturn = new List(); + + // Name is required and always available + toReturn.Add("Name (string, required)"); + + // And animation is required too + toReturn.Add(animationColumnName); + + toReturn.AddRange(this.Tilesets.SelectMany(t => t.Tiles) + .SelectMany(tile => tile.PropertyDictionary) + .Select(d => d.Key) + //.Distinct(comparer) + .ToList()); + + foreach (var group in this.objectgroup) + { + bool addedGroup = false; + foreach (var @object in group.@object) + { + if (@object.gid != null) + { + addedGroup = true; + toReturn.AddRange(@object.PropertyDictionary.Keys); + } + } + if (addedGroup) + { + toReturn.AddRange(group.PropertyDictionary.Keys); + } + } + + return toReturn.Distinct(comparer); + } + + + public NodeNetwork ToNodeNetwork(bool requireTile = true) + { + return ToNodeNetwork(true, true, true, requireTile); + } + + public NodeNetworkSave ToNodeNetworkSave(bool linkHorizontally, bool linkVertically, bool linkDiagonally, bool requireTile) + { + NodeNetwork nodeNetwork = ToNodeNetwork(linkHorizontally, linkVertically, linkDiagonally, requireTile); + return NodeNetworkSave.FromNodeNetwork(nodeNetwork); + } + + public NodeNetworkSave ToNodeNetworkSave(bool requireTile = true) + { + return ToNodeNetworkSave(true, true, true, requireTile); + } + + public NodeNetwork ToNodeNetwork(bool linkHorizontally, bool linkVertically, bool linkDiagonally, bool requireTile) + { + var toReturn = new NodeNetwork(); + + + int layercount = 0; + foreach (MapLayer mapLayer in this.Layers) + { + if (!mapLayer.IsVisible) + { + switch (mapLayer.VisibleBehavior) + { + case LayerVisibleBehavior.Ignore: + break; + case LayerVisibleBehavior.Skip: + continue; + } + } + var allNodes = new Dictionary>>(); + allNodes[layercount] = new Dictionary>(); + + + MapLayer mLayer = mapLayer; + int mLayerCount = layercount; + Parallel.For(0, mapLayer.data[0].tiles.Count, count => + { + uint gid = mLayer.data[0].tiles[count]; + + Tileset tileSet = GetTilesetForGid(gid); + if (tileSet != null || !requireTile) + { + var node = new PositionedNode(); + + //int tileWidth = requireTile ? tileSet.tilewidth : tilewidth; + //int tileHeight = requireTile ? tileSet.tileheight : tileheight; + int x = count % this.Width; + int y = count / this.Width; + + float nodex; + float nodey; + float nodez; + + CalculateWorldCoordinates(mLayerCount, count, tilewidth, tileheight, mLayer.width, out nodex, out nodey, out nodez); + + node.X = nodex; + node.Y = nodey; + node.Z = nodez; + + lock (allNodes) + { + if (!allNodes[mLayerCount].ContainsKey(x)) + { + allNodes[mLayerCount][x] = new Dictionary(); + } + + allNodes[mLayerCount][x][y] = node; + } + node.Name = string.Format("Node {0}", count); + lock (toReturn) + { + toReturn.AddNode(node); + } + } + }); + SetupNodeLinks(linkHorizontally, linkVertically, linkDiagonally, allNodes[layercount]); + + RemoveExcludedNodesViaPolygonLayer(toReturn, mapLayer, allNodes[layercount]); + LowerNodesInNodesDownShapeCollection(mapLayer, allNodes[layercount]); + RaiseNodesInNodesUpShapeCollection(mapLayer, allNodes[layercount]); + + ++layercount; + } + toReturn.UpdateShapes(); + + return toReturn; + } + + private void RaiseNodesInNodesUpShapeCollection(MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nodesup"); + List nodesToMoveUp = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToMoveUp) + { + node.Z += .001f; + } + } + + private void LowerNodesInNodesDownShapeCollection(MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nodesdown"); + List nodesToMoveDown = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToMoveDown) + { + node.Z -= .001f; + } + } + + private void RemoveExcludedNodesViaPolygonLayer(NodeNetwork nodeNetwork, MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nonodes"); + List nodesToRemove = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToRemove) + { + nodeNetwork.Remove(node); + } + } + + private List GetNodesThatCollideWithShapeCollection(ShapeCollection sc, Dictionary> allNodes) + { + var returnValue = new List(); + + if (sc != null && sc.Polygons != null) + { + foreach (Polygon polygon in sc.Polygons) + { + polygon.ForceUpdateDependencies(); + } + + foreach (var xpair in allNodes) + { + foreach (var ypair in xpair.Value) + { + PositionedNode node = ypair.Value; + var rectangle = new AxisAlignedRectangle { Position = node.Position, ScaleX = 1, ScaleY = 1 }; + + if (sc.CollideAgainst(rectangle)) + { + returnValue.Add(node); + } + } + } + } + return returnValue; + } + + private static void SetupNodeLinks(bool linkHorizontally, bool linkVertically, bool linkDiagonally, Dictionary> allNodes) + { + foreach (var xpair in allNodes) + { + int x = xpair.Key; + foreach (var ypair in xpair.Value) + { + int y = ypair.Key; + + if (linkVertically && allNodes.ContainsKey(x - 1) && allNodes[x - 1].ContainsKey(y)) + { + float cost = (ypair.Value.Position - allNodes[x - 1][y].Position).Length(); + ypair.Value.LinkTo(allNodes[x - 1][y], cost); + } + if (linkHorizontally && xpair.Value.ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - xpair.Value[y - 1].Position).Length(); + ypair.Value.LinkTo(xpair.Value[y - 1], cost); + } + if (linkDiagonally && allNodes.ContainsKey(x - 1) && allNodes[x - 1].ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - allNodes[x - 1][y - 1].Position).Length(); + ypair.Value.LinkTo(allNodes[x - 1][y - 1], cost); + } + if (linkDiagonally && allNodes.ContainsKey(x + 1) && allNodes[x + 1].ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - allNodes[x + 1][y - 1].Position).Length(); + ypair.Value.LinkTo(allNodes[x + 1][y - 1], cost); + } + } + } + } + + public SceneSave ToSceneSave(float scale, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + var toReturn = new SceneSave { CoordinateSystem = FlatRedBall.Math.CoordinateSystem.RightHanded }; + + // TODO: Somehow add all layers separately + for (int layercount = 0; layercount < this.MapLayers.Count; layercount++) + { + var abstractMapLayer = this.MapLayers[layercount]; + var mapLayer = abstractMapLayer as MapLayer; + + if (mapLayer != null) + { + if (!mapLayer.IsVisible) + { + switch (mapLayer.VisibleBehavior) + { + case LayerVisibleBehavior.Ignore: + break; + case LayerVisibleBehavior.Skip: + continue; + } + } + + MapLayer mLayer = mapLayer; + int mLayerCount = layercount; + + for (int i = 0; i < mapLayer.data[0].tiles.Count; i++) + { + uint gid = mLayer.data[0].tiles[i]; + if (gid > 0) + { + Tileset tileSet = GetTilesetForGid(gid); + if (tileSet != null) + { + SpriteSave sprite = CreateSpriteSaveFromMapTileset(scale, mLayerCount, mLayer, i, gid, + tileSet, referenceType); + lock (toReturn) + { + toReturn.SpriteList.Add(sprite); + } + } + } + } + continue; + } + + var group = abstractMapLayer as mapObjectgroup; + bool shouldProcess = group?.@object != null && group.Visible && !string.IsNullOrEmpty(group.Name); + if (shouldProcess) //&& (string.IsNullOrEmpty(layerName) || group.name.Equals(layerName))) + { + foreach (mapObjectgroupObject @object in @group.@object) + { + if (@object.gid != null) + { + SpriteSave sprite = CreateSpriteSaveFromObject(scale, @object, layercount, referenceType); + lock (toReturn) + { + toReturn.SpriteList.Add(sprite); + } + } + } + } + } + + return toReturn; + } + + private SpriteSave CreateSpriteSaveFromObject(float scale, mapObjectgroupObject @object, int layerCount, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + + if (@object.gid == null) + { + throw new NotSupportedException("CreateSpriteSaveFromObject called on a non image object. gid not set."); + } + + var gid = @object.gid.Value; + + Tileset tileSet = GetTilesetForGid(gid); + + var sprite = new SpriteSave(); + //if (!mapLayer.IsVisible && mapLayer.VisibleBehavior == LayerVisibleBehavior.Match) + //{ + // sprite.Visible = false; + //} + + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + // TODO: only calculate these once per tileset. Perhaps it can be done in the deserialize method + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + //int tilesHigh = (imageHeight - margin) / (tileHeight + spacing); + + if (referenceType == FileReferenceType.NoDirectory) + { + sprite.Texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + string directory = FileManager.GetDirectory(this.FileName); + + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FileManager.RemoveDotDotSlash(directory); + + } + + sprite.Texture = FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + + uint tileTextureRelativeToStartOfTileset = + (0x0fffffff & gid) - tileSet.Firstgid + 1; + + //if (tileSet.TileDictionary.ContainsKey(tileTextureRelativeToStartOfTileset)) + //{ + // var dictionary = tileSet.TileDictionary[tileTextureRelativeToStartOfTileset].PropertyDictionary; + + // foreach (var kvp in dictionary) + // { + // var key = kvp.Key; + + // if (IsName(key)) + // { + // sprite.Name = kvp.Value; + // } + // } + //} + + // This is bad - we want to ue the same names as in Tiled so we don't accidentally + // apply properties from the wrong tiles + //if (string.IsNullOrEmpty(@object.Name)) + //{ + // sprite.Name = "Unnamed" + gid; + //} + //else + { + sprite.Name = @object.Name; + } + SetSpriteTextureCoordinates(gid, sprite, tileSet, this.orientation); + + //CalculateWorldCoordinates(layercount, tileIndex, tileWidth, tileHeight, this.Width, out sprite.X, out sprite.Y, out sprite.Z); + sprite.X = (float)@object.x; + sprite.Y = -(float)@object.y; + sprite.Z = layerCount; + + //sprite.ScaleX = tileWidth / 2.0f; + //sprite.ScaleY = tileHeight / 2.0f; + sprite.ScaleX = (float)@object.width / 2.0f; + sprite.ScaleY = (float)@object.height / 2.0f; + + ///Is the tileset offset necessary for this? + //if (tileSet.Tileoffset != null && tileSet.Tileoffset.Length == 1) + //{ + // sprite.X += tileSet.Tileoffset[0].x; + // sprite.Y -= tileSet.Tileoffset[0].y; + //} + + sprite.X *= scale; + sprite.Y *= scale; + // Update August 28, 2012 + // The TMX converter splits + // the Layers by their Z values. + // We want each Layer to have its + // own explicit Z value, so we don't + // want to adjust the Z's when we scale: + //sprite.Z *= scale; + + sprite.ScaleX *= scale; + sprite.ScaleY *= scale; + + sprite.X += sprite.ScaleX; + sprite.Y += sprite.ScaleY; + return sprite; + } + private SpriteSave CreateSpriteSaveFromMapTileset(float scale, int layercount, MapLayer mapLayer, int tileIndex, uint gid, Tileset tileSet, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + var sprite = new SpriteSave(); + if (!mapLayer.IsVisible && mapLayer.VisibleBehavior == LayerVisibleBehavior.Match) + { + sprite.Visible = false; + } + + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + // TODO: only calculate these once per tileset. Perhaps it can be done in the deserialize method + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + //int tilesHigh = (imageHeight - margin) / (tileHeight + spacing); + + if (referenceType == FileReferenceType.NoDirectory) + { + sprite.Texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + string directory = FileManager.GetDirectory(this.FileName); + + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FileManager.RemoveDotDotSlash(directory); + + } + + sprite.Texture = FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + + uint tileTextureRelativeToStartOfTileset = + (0x0fffffff & gid) - tileSet.Firstgid; + + if (tileSet.TileDictionary.ContainsKey(tileTextureRelativeToStartOfTileset)) + { + var dictionary = tileSet.TileDictionary[tileTextureRelativeToStartOfTileset].PropertyDictionary; + + foreach (var kvp in dictionary) + { + var key = kvp.Key; + + if (IsName(key)) + { + sprite.Name = kvp.Value; + } + } + } + + // This can cause tiles to use properties from other tiles, so this is bad. If no name exists in Tiled, + // no name should be given here: + //if (string.IsNullOrEmpty(sprite.Name)) + //{ + // sprite.Name = "Unnamed" + gid; + //} + + SetSpriteTextureCoordinates(gid, sprite, tileSet, this.orientation); + CalculateWorldCoordinates(layercount, tileIndex, tileWidth, tileHeight, this.Width, out sprite.X, out sprite.Y, out sprite.Z); + + sprite.ScaleX = tileWidth / 2.0f; + sprite.ScaleY = tileHeight / 2.0f; + + if (tileSet.Tileoffset != null && tileSet.Tileoffset.Length == 1) + { + sprite.X += tileSet.Tileoffset[0].x; + sprite.Y -= tileSet.Tileoffset[0].y; + } + + + sprite.X *= scale; + sprite.Y *= scale; + // Update August 28, 2012 + // The TMX converter splits + // the Layers by their Z values. + // We want each Layer to have its + // own explicit Z value, so we don't + // want to adjust the Z's when we scale: + //sprite.Z *= scale; + + sprite.ScaleX *= scale; + sprite.ScaleY *= scale; + return sprite; + } + + private static bool IsName(string key) + { + return property.GetStrippedName(key).ToLower() == "name"; + } + + public void CalculateWorldCoordinates(int layerIndex, int tileIndex, int tileWidth, int tileHeight, int layerWidth, out float x, out float y, out float z) + { + int normalizedX = tileIndex % this.Width; + int normalizedY = tileIndex / this.Width; + CalculateWorldCoordinates(layerIndex, normalizedX, normalizedY, tileWidth, tileHeight, layerWidth, out x, out y, out z); + } + + public void CalculateWorldCoordinates(int layerIndex, float normalizedX, float normalizedY, int tileWidth, int tileHeight, int layerWidth, out float x, out float y, out float z) + { + if (this.orientation == null || this.orientation.Equals("orthogonal")) + { + x = (normalizedX * this.tilewidth) + (this.tilewidth / 2.0f); + x += (tileWidth - this.tilewidth) / 2.0f; + y = -(normalizedY * this.tileheight) - (this.tileheight / 2.0f); + y += (tileHeight - this.tileheight) / 2.0f; + z = layerIndex; + } + else if (this.orientation != null && this.orientation.Equals("isometric")) + { + y = -((normalizedX * this.tilewidth / 2.0f) + (normalizedY * this.tilewidth / 2.0f)) / 2; + y += tileHeight / 2.0f; + x = -((normalizedY * this.tilewidth / 2.0f) - (normalizedX * this.tileheight / 2.0f) * 2); + x += tileWidth / 2.0f; + z = ((normalizedY * layerWidth + normalizedX) * .000001f) + layerIndex; + } + else + { + throw new NotImplementedException("Unknown orientation type"); + } + + x += Offset.Item1; + y += Offset.Item2; + z += Offset.Item3; + } + + public static void SetSpriteTextureCoordinates(uint gid, SpriteSave sprite, Tileset tileSet, string orientation) + { + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + + int leftPixelCoord; + int topPixelCoord; + int rightPixelCoord; + int bottomPixelCoord; + GetPixelCoordinatesFromGid(gid, tileSet, + out leftPixelCoord, out topPixelCoord, out rightPixelCoord, out bottomPixelCoord); + + + bool flipDiagonally; + var gidWithoutRotation = gid & 0x0fffffff; + const uint FlippedDiagonallyFlag = 0x20000000; + flipDiagonally = (gid & FlippedDiagonallyFlag) == FlippedDiagonallyFlag; + + + //if (flipDiagonally) + //{ + // // this turns: + // // 1---2 + // // | | + // // 3---4 + + // // into: + // // 1---3 + // // | | + // // 2---4 + + // int newLeft = topPixelCoord; + // int newRight = bottomPixelCoord; + // int newTop = leftPixelCoord; + // int newBottom = rightPixelCoord; + + // topPixelCoord = newTop; + // bottomPixelCoord = newBottom; + // leftPixelCoord = newLeft; + // rightPixelCoord = newRight; + //} + + // Calculate relative texture coordinates based on pixel coordinates + var changeVal = LessOrGreaterDesired.Greater; + + if (orientation == "isometric") + { + changeVal = LessOrGreaterDesired.NoChange; + } + + sprite.TopTextureCoordinate = GetTextureCoordinate(topPixelCoord, imageHeight, changeVal); + sprite.LeftTextureCoordinate = GetTextureCoordinate(leftPixelCoord, imageWidth, changeVal); + + changeVal = LessOrGreaterDesired.Less; + if (orientation == "isometric") + { + changeVal = LessOrGreaterDesired.NoChange; + } + + sprite.RightTextureCoordinate = GetTextureCoordinate(rightPixelCoord, imageWidth, changeVal); + sprite.BottomTextureCoordinate = GetTextureCoordinate(bottomPixelCoord, imageHeight, changeVal); + + if (flipDiagonally) + { + sprite.RotationZ = Microsoft.Xna.Framework.MathHelper.PiOver2; + sprite.FlipHorizontal = true; + } + } + + public static void GetPixelCoordinatesFromGid(uint gid, Tileset tileSet, + out int leftPixelCoord, out int topPixelCoord, out int rightPixelCoord, out int bottomPixelCoord) + { + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + + var gidWithoutRotation = gid & 0x0fffffff; + + const uint FlippedHorizontallyFlag = 0x80000000; + const uint FlippedVerticallyFlag = 0x40000000; + const uint FlippedDiagonallyFlag = 0x20000000; + + bool flipHorizontally = (gid & FlippedHorizontallyFlag) == FlippedHorizontallyFlag; + bool flipVertically = (gid & FlippedVerticallyFlag) == FlippedVerticallyFlag; + bool flipDiagonally = (gid & FlippedDiagonallyFlag) == FlippedDiagonallyFlag; + + // Calculate pixel coordinates in the texture sheet + leftPixelCoord = CalculateXCoordinate(gidWithoutRotation - tileSet.Firstgid, imageWidth, tileWidth, spacing, margin); + topPixelCoord = CalculateYCoordinate(gidWithoutRotation - tileSet.Firstgid, imageWidth, tileWidth, tileHeight, spacing, margin); + rightPixelCoord = leftPixelCoord + tileWidth; + bottomPixelCoord = topPixelCoord + tileHeight; + + if ((flipHorizontally && flipDiagonally == false) || + (flipVertically && flipDiagonally)) + { + var temp = rightPixelCoord; + rightPixelCoord = leftPixelCoord; + leftPixelCoord = temp; + } + + if ((flipVertically && flipDiagonally == false) || + (flipHorizontally && flipDiagonally)) + { + var temp = topPixelCoord; + topPixelCoord = bottomPixelCoord; + bottomPixelCoord = temp; + + } + } + + public Tileset GetTilesetForGid(uint gid, bool shouldRemoveFlipFlags = true) + { + var effectiveGid = gid; + + if (shouldRemoveFlipFlags) + { + effectiveGid = 0x0fffffff & gid; + } + + // Assuming tilesets are sorted by the firstgid value... + // Resort with LINQ if not + if (Tilesets != null) + { + for (int i = Tilesets.Count - 1; i >= 0; --i) + { + Tileset tileSet = Tilesets[i]; + if (effectiveGid >= tileSet.Firstgid) + { + return tileSet; + } + } + } + return null; + } + + private static float GetTextureCoordinate(int pixelCoord, int dimension, LessOrGreaterDesired lessOrGreaterDesired) + { + float asFloat = pixelCoord / (float)dimension; + + //const float modValue = .000001f; + const float modValue = .000002f; + //const float modValue = .00001f; + switch (lessOrGreaterDesired) + { + case LessOrGreaterDesired.Greater: + return asFloat + modValue; + case LessOrGreaterDesired.Less: + return asFloat - modValue; + default: + return asFloat; + } + } + + public static int CalculateYCoordinate(uint gid, int imageWidth, int tileWidth, int tileHeight, int spacing, int margin) + { + + int tilesWide = TilesetExtensionMethods.GetNumberOfTilesWide( + imageWidth, margin, tileWidth, spacing); + + int normalizedy = (int)(gid / tilesWide); + int pixely = normalizedy * (tileHeight + spacing) + margin; + + return pixely; + } + + public static int CalculateXCoordinate(uint gid, int imageWidth, int tileWidth, int spacing, int margin) + { + var tilesWide = TilesetExtensionMethods.GetNumberOfTilesWide( + imageWidth, margin, tileWidth, spacing); + + + int normalizedX = (int)(gid % tilesWide); + int pixelX = normalizedX * (tileWidth + spacing) + margin; + + return pixelX; + } + + public static TiledMapSave FromFile(string fileName) + { + if (FileManager.IsRelative(fileName)) + { + fileName = FileManager.RelativeDirectory + fileName; + } + + // I believe the relative directory of the TMS must be its own directory so that + // image references can be tracked on XML deserialization + string oldRelativeDirectory = FileManager.RelativeDirectory; + try + { + FileManager.RelativeDirectory = FileManager.GetDirectory(fileName); + } + catch + { + } + TiledMapSave tms = null; + + try + { + tms = FileManager.XmlDeserialize(fileName); + tms.FileName = fileName; + } + finally + { + FileManager.RelativeDirectory = oldRelativeDirectory; + + } + + + + + foreach (MapLayer layer in tms.Layers) + { + if (!layer.PropertyDictionary.ContainsKey("VisibleBehavior")) + { + layer.VisibleBehavior = LayerVisibleBehaviorValue; + } + else + { + if (!Enum.TryParse(layer.PropertyDictionary["VisibleBehavior"], out layer.VisibleBehavior)) + { + layer.VisibleBehavior = LayerVisibleBehaviorValue; + } + } + } + return tms; + } + + public void Save(string fileName) + { + FileManager.XmlSerialize(this, fileName); + + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Serialization.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Serialization.cs new file mode 100644 index 000000000..69f3aa0e2 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapSave.Serialization.cs @@ -0,0 +1,993 @@ +using System.Xml.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Linq; +using System.Xml.Schema; + +// +// This source code was auto-generated by xsd, Version=2.0.50727.3038. +// +namespace TMXGlueLib +{ + #region TiledMapSave Class + /// + [XmlType(AnonymousType = true)] + [XmlRoot(ElementName = "map", Namespace = "", IsNullable = false)] + public partial class TiledMapSave + { + + public enum LayerVisibleBehavior { Ignore, Match, Skip }; + + #region Fields + + private IDictionary propertyDictionaryField = null; + + List mProperties = new List(); + #endregion + + [XmlIgnore] + public string FileName + { + get; + set; + } + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", "map"); + } + return propertyDictionaryField; + } + } + } + + public static IDictionary BuildPropertyDictionaryConcurrently(IEnumerable properties) + { + ConcurrentDictionary propertyDictionary = new ConcurrentDictionary(); + Parallel.ForEach(properties, (p) => + { + if (p != null && !propertyDictionary.ContainsKey(p.name)) + { + // Don't ToLower it - it causes problems when we try to get the column name out again. + //propertyDictionaryField.Add(p.name.ToLower(), p.value); + + propertyDictionary[p.name] = p.value; + } + }); + return propertyDictionary; + } + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + public bool ShouldSerializeproperties() + { + return mProperties != null && mProperties.Count != 0; + } + + + /// + [XmlElement("tileset", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public List Tilesets + { + get; + set; + } + + + [XmlElement("layer", typeof(MapLayer))] + [XmlElement("imagelayer", typeof(MapImageLayer))] + [XmlElement("objectgroup", typeof(mapObjectgroup))] + public List MapLayers { get; set; } + + /// + + + [XmlIgnore] + public List Layers => MapLayers.OfType().ToList(); + + /// + [XmlIgnore] + public List objectgroup => MapLayers.OfType().ToList(); + + [XmlIgnore] + public List ImageLayers => MapLayers.OfType().ToList(); + + /// + [XmlAttribute()] + public string version + { + get; + set; + } + + /// + [XmlAttribute()] + public string orientation + { + get; + set; + } + + /// + /// The number of cells this map has on the X axis. + /// + [XmlAttribute("width")] + public int Width + { + get; + set; + } + + /// + /// The number of cells this map has on the Y axis. + /// + [XmlAttribute("height")] + public int Height + { + get; + set; + } + + /// + [XmlAttribute()] + public int tilewidth + { + get; + set; + } + + /// + [XmlAttribute()] + public int tileheight + { + get; + set; + } + + public TiledMapSave() + { + MapLayers = new List(); + Tilesets = new List(); + } + + public List GetReferencedFiles() + { + List referencedFiles = new List(); + + if(this.Tilesets != null) + { + foreach (var tileset in this.Tilesets) + { + if(!string.IsNullOrEmpty(tileset.Source )) + { + referencedFiles.Add(tileset.Source); + } + else if (tileset?.Images != null && tileset.Images.Length != 0) + { + var image = tileset.Images[0]; + + string fileName = image.Source; + + // keep it relative + referencedFiles.Add(fileName); + + } + + } + } + + + return referencedFiles; + } + } + + #endregion + + #region property Class + public partial class property + { + [XmlAttribute()] + public string name + { + get; + set; + } + + [XmlAttribute()] + public string value + { + get; + set; + } + + [XmlAttribute("type")] + public string Type { get; set; } + + [XmlIgnore] + public string StrippedName + { + get + { + return GetStrippedName(this.name); + } + } + + public string StrippedNameLower + { + get + { + return GetStrippedName(this.name).ToLowerInvariant(); + } + } + + + public static string GetStrippedName(string name) + { + string nameWithoutType; + if (name.Contains('(') && name.Contains(')')) + { + int open = name.IndexOf('('); + int close = name.IndexOf(')'); + + nameWithoutType = name.Substring(0, open).Trim(); + } + else + { + nameWithoutType = name; + } + + return nameWithoutType; + } + + public static string GetPropertyName(string name) + { + if (name.Contains('(') && name.Contains(')')) + { + int open = name.IndexOf('('); + int close = name.IndexOf(')'); + + int afterOpen = open+1; + + return name.Substring(afterOpen, close - afterOpen); + + } + else + { + return null; + } + + } + + + + public override string ToString() + { + return name + " = " + value; + } + } + #endregion + +#if !UWP + [Serializable] +#endif + public partial class MapImageLayer : AbstractMapLayer + { + private MapImageLayerImage imageField; + + private float _offsetX; + private float _offsetY; + private float _opacity; + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + private IDictionary propertyDictionaryField = null; + private int _visibleAsInt = 1; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("image", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public MapImageLayerImage ImageObject + { + get + { + return this.imageField; + } + set + { + this.imageField = value; + } + } + + + [XmlAttribute("visible")] + public int VisibleAsInt + { + get { return _visibleAsInt; } + set { _visibleAsInt = value; } + } + + + [XmlAttribute("opacity")] + public float Opacity + { + get { return _opacity; } + set { _opacity = value; } + } + + [XmlAttribute("offsetx")] + public float OffsetX + { + get { return _offsetX; } + set { _offsetX = value; } + } + + [XmlAttribute("offsety")] + public float OffsetY + { + get { return _offsetY; } + set { _offsetY = value; } + } + + [XmlIgnore] + public bool Visible + { + get + { + return VisibleAsInt != 0; + } + } + } + + public partial class MapImageLayerImage + { + private string _source; + + [XmlAttributeAttribute(AttributeName = "source")] + public string Source + { + get { return _source; } + set { _source = value; } + } + + /// + [XmlAttributeAttribute(AttributeName = "width")] + public float Width { get; set; } + + /// + [XmlAttributeAttribute(AttributeName = "height")] + public float Height { get; set; } + } + + + /// + [XmlType(AnonymousType = true)] + [XmlRoot(ElementName = "mapTilesetImage", Namespace = "", IsNullable = false)] + public partial class TilesetImage + { + + private string sourceField; + + /// + [XmlAttribute("source")] + public string Source + { + get + { + return this.sourceField; + } + set + { + this.sourceField = value; + if (this.sourceField != null) + { + this.sourceField = this.sourceField.Replace("/", "\\"); + } + } + } + + [XmlIgnore] + public string sourceFileName + { + get + { + if (!string.IsNullOrEmpty(Source) && Source.Contains("\\")) + { + return Source.Substring(Source.LastIndexOf('\\') + 1); + } + else + { + return Source; + } + } + } + + [XmlIgnore] + public string sourceDirectory + { + get + { + if (!string.IsNullOrEmpty(Source) && Source.Contains("\\")) + { + return Source.Substring(0, Source.LastIndexOf('\\')); + } + else + { + return Source; + } + } + } + + + /// + [XmlAttribute()] + public int width + { + get; + set; + } + + /// + [XmlAttribute()] + public int height + { + get; + set; + } + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapTilesetTileOffset + { + + private int xField; + + private int yField; + + /// + [XmlAttribute()] + public int x + { + get + { + return xField; + } + set + { + xField = value; + } + } + + /// + [XmlAttribute()] + public int y + { + get + { + return yField; + } + set + { + yField = value; + } + } + } + + + + public partial class mapLayerDataTile + { + [XmlAttribute()] + public string gid { get; set; } + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapLayerData + { + + private string encodingField; + + private string compressionField; + + private string valueField; + + /// + [XmlAttribute()] + public string encoding + { + get + { + return this.encodingField; + } + set + { + this.encodingField = value; + } + } + + /// + [XmlAttribute()] + public string compression + { + get + { + return this.compressionField; + } + set + { + this.compressionField = value; + } + } + + [XmlElement("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapLayerDataTile[] dataTiles { get; set; } + + /// + [XmlText()] + public string Value + { + get + { + return this.valueField; + } + set + { + this.valueField = value; + } + } + + + /// + /// Represents the index that this tile is displaying from the source tile map. This is 1-based. 0 means no tile. + /// This can span multiple tilesets. + /// + private List _ids = null; + + + /// + /// Represents all of the tiles in this layer. A tile with index 0 means there is no tile there. Non-zero values + /// mean that the value is painted. Painted values are global IDs of tiles. Index 0 is the top-left tile. Increasing + /// the index moves towards the right. When reading the end of the row, the next index represents the first tile in the + /// next row. + /// + [XmlIgnore] + public List tiles + { + get + { + if (encodingField != "base64" && encodingField != null && encodingField != "csv") + { + throw new NotImplementedException("Unknown encoding: " + encodingField); + } + + if (_ids == null) + { + if (encodingField != null && encodingField != "csv") + { + _ids = new List(length); + // get a stream to the decoded Base64 text + + var trimmedValue = Value.Trim(); + + Stream data = new MemoryStream(Convert.FromBase64String(trimmedValue), false); + switch (compression) + { + case "gzip": + data = new GZipStream(data, CompressionMode.Decompress, false); + break; + case "zlib": +#if SUPPORTS_ZLIB + data = new Ionic.Zlib.ZlibStream(data, Ionic.Zlib.CompressionMode.Decompress, false); +#else + throw new NotImplementedException("Does not support zlib"); +#endif + break; + case null: + // Not compressed. Data is already decoded. + break; + default: + throw new InvalidOperationException("Unknown compression: " + compression); + } + + // simply read in all the integers + using (data) + { + using (BinaryReader reader = new BinaryReader(data)) + { + _ids = new List(); + for (int i = 0; i < length; i++) + { + _ids.Add(reader.ReadUInt32()); + } + } + } + } + else if (encodingField == "csv") + { + string[] idStrs = Value.Split(",\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + _ids = idStrs.AsParallel().Select(id => + { + uint gid; + if (!uint.TryParse(id, out gid)) + { + gid = 0; + } + return gid; + }).ToList(); + } + else if (encodingField == null) + { + _ids = dataTiles.AsParallel().Select(dt => + { + uint gid; + if (!uint.TryParse(dt.gid, out gid)) + { + gid = 0; + } + return gid; + }).ToList(); + } + } + + return _ids; + } + + } + + public int length { get; set; } + } + +#if !UWP + [Serializable] +#endif + public partial class mapObjectgroup : AbstractMapLayer + { + private mapObjectgroupObject[] objectField; + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + private IDictionary propertyDictionaryField = null; + private int _visibleAsInt = 1; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("object", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObject[] @object + { + get + { + return this.objectField; + } + set + { + this.objectField = value; + } + } + + + [XmlAttribute("visible")] + public int VisibleAsInt + { + get { return _visibleAsInt; } + set { _visibleAsInt = value; } + } + + [XmlIgnore] + public bool Visible + { + get + { + return VisibleAsInt != 0; + } + } + + public override string ToString() + { + return Name; + } + } + + /// + public partial class mapObjectgroupObject + { + private mapObjectgroupObjectEllipse ellipseField = null; + + private mapObjectgroupObjectPolygon[] polygonField; + + private mapObjectgroupObjectPolyline[] polylineField; + + private double xField; + + private double yField; + + private string _name; + + + [XmlAttributeAttribute(AttributeName = "name")] + public string Name + { + get { return _name; } + set { _name = value; } + } + + [XmlAttribute(AttributeName = "gid")] + public string __proxygid + { + get { return gid?.ToString(); } + set { gid = uint.Parse(value); } + } + + [XmlIgnore] + public uint? gid { get; set; } + + private IDictionary propertyDictionaryField = null; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!string.IsNullOrEmpty(this.Name) && !propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", this.Name); + } + return propertyDictionaryField; + } + } + } + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + [XmlElement("ellipse", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectEllipse ellipse + { + get + { + return ellipseField; + } + set + { + ellipseField = value; + } + } + + /// + [XmlElement("polygon", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectPolygon[] polygon + { + get + { + return this.polygonField; + } + set + { + this.polygonField = value; + } + } + + /// + [XmlElement("polyline", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectPolyline[] polyline + { + get + { + return this.polylineField; + } + set + { + this.polylineField = value; + } + } + + /// + [XmlAttribute("x")] + public double x + { + get + { + return this.xField; + } + set + { + this.xField = value; + } + } + + /// + [XmlAttribute("y")] + public double y + { + get + { + return this.yField; + } + set + { + this.yField = value; + } + } + + /// + [XmlAttribute()] + public float width { get; set; } + + /// + [XmlAttribute()] + public float height { get; set; } + + [XmlAttribute("rotation")] + public double Rotation + { + get; + set; + } + } + + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectEllipse + { + + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectPolygon + { + + private string pointsField; + + /// + [XmlAttribute()] + public string points + { + get + { + return this.pointsField; + } + set + { + this.pointsField = value; + } + } + } + +#region mapObjectgroupObjectPolyline + + /// + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectPolyline + { + + private string pointsField; + + /// + [XmlAttribute()] + public string points + { + get + { + return this.pointsField; + } + set + { + this.pointsField = value; + } + } + } + +#endregion + +#region NewDataSet Class + /// + [XmlType(AnonymousType = true)] + [XmlRoot(Namespace = "", IsNullable = false)] + public partial class NewDataSet + { + + private TiledMapSave[] itemsField; + + /// + [XmlElement("map")] + public TiledMapSave[] Items + { + get + { + return this.itemsField; + } + set + { + this.itemsField = value; + } + } + } + +#endregion + +} \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapToShapeCollectionConverter.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapToShapeCollectionConverter.cs new file mode 100644 index 000000000..579078160 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TiledMapToShapeCollectionConverter.cs @@ -0,0 +1,268 @@ +using FlatRedBall.Content.Math.Geometry; +using FlatRedBall.Content.Polygon; +using FlatRedBall.Math.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Point = FlatRedBall.Math.Geometry.Point; + +namespace TMXGlueLib +{ + public static class TiledMapToShapeCollectionConverter + { + public static ShapeCollection ToShapeCollection(this TiledMapSave tiledMapSave, string layerName) + { + MapLayer mapLayer = null; + if (!string.IsNullOrEmpty(layerName)) + { + mapLayer = tiledMapSave.Layers.FirstOrDefault(l => l.Name.Equals(layerName)); + } + var shapes = new ShapeCollection(); + + if ((mapLayer != null && !mapLayer.IsVisible && mapLayer.VisibleBehavior == TMXGlueLib.TiledMapSave.LayerVisibleBehavior.Skip) || + tiledMapSave.objectgroup == null || tiledMapSave.objectgroup.Count == 0) + { + return shapes; + } + + foreach (mapObjectgroup group in tiledMapSave.objectgroup) + { + if (group.@object != null && !string.IsNullOrEmpty(group.Name) && (string.IsNullOrEmpty(layerName) || group.Name.Equals(layerName))) + { + foreach (mapObjectgroupObject @object in group.@object) + { + AddShapeToShapeCollection(@object, shapes); + } + + } + } + return shapes; + } + + public static void AddShapeToShapeCollection(mapObjectgroupObject @object, ShapeCollection shapes) + { + + //////////////////////////Early out//////////////////////////////// + ///November 8th, 2015 + ///Jesse Crafts-Finch + ///If a polygon has a gid, and therefore an image associate with it, it will be turned into a spritesave, not a polygon. + if (@object.gid != null) + { + return; + } + ////////////////////////End Early Out///////////////////////////////// + Polygon polygon; + AxisAlignedRectangle rectangle; + Circle circle; + + ConvertTiledObjectToFrbShape(@object, out polygon, out rectangle, out circle); + + if (polygon != null) + { + shapes.Polygons.Add(polygon); + } + if (rectangle != null) + { + shapes.AxisAlignedRectangles.Add(rectangle); + } + if (circle != null) + { + shapes.Circles.Add(circle); + } + } + + public static void ConvertTiledObjectToFrbShape(mapObjectgroupObject @object, out Polygon polygon, out AxisAlignedRectangle rectangle, out Circle circle) + { + polygon = null; + rectangle = null; + circle = null; + if (@object.polygon != null) + { + foreach (mapObjectgroupObjectPolygon tiledPolygon in @object.polygon) + { + // TODO: Make this a rectangle object + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, + @object.x, @object.y, @object.Rotation, tiledPolygon.points, true); + } + } + + if (@object.polyline != null) + { + foreach (mapObjectgroupObjectPolyline polyline in @object.polyline) + { + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, + @object.x, @object.y, @object.Rotation, polyline.points, false); + } + } + + if (@object.polygon == null && @object.polyline == null) + { + if (@object.Rotation == 0 && @object.ellipse == null) + { + rectangle = new AxisAlignedRectangle() + { + Name = @object.Name, + X = (float)@object.x + (@object.width / 2), + Y = (float)-@object.y - (@object.height / 2), + ScaleX = @object.width / 2, + ScaleY = @object.height / 2, + }; + + } + else if (@object.ellipse != null && @object.width == @object.height) + { + circle = new Circle() + { + Name = @object.Name, + X = (float)@object.x + (@object.width / 2), + Y = (float)-@object.y - (@object.height / 2), + Radius = @object.width / 2 + }; + + } + else + { + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, @object.x, @object.y, @object.width, @object.height, @object.Rotation, @object.ellipse); + } + } + } + + private static Polygon ConvertTmxObjectToFrbPolygon(string name, + double x, double y, double w, double h, double rotation, mapObjectgroupObjectEllipse ellipse) + { + var pointsSb = new StringBuilder(); + + if (ellipse == null) + { + pointsSb.AppendFormat("{0},{1}", -w / 2, -h / 2); + + pointsSb.AppendFormat(" {0},{1}", w / 2, -h / 2); + pointsSb.AppendFormat(" {0},{1}", w / 2, h / 2); + pointsSb.AppendFormat(" {0},{1}", -w / 2, h / 2); + } + else + { + const double a = .5; + const double b = .5; + + // x = a cos t + // y = b cos t + var first = true; + string firstPoint = ""; + for (var angle = 0; angle <= 360; angle += 18) + { + var radians = MathHelper.ToRadians(angle); + + + // This code made the position of the poly be top left, not optimized! + //var newx = a*Math.Cos(radians)*w+w/2; + //var newy = b*Math.Sin(radians)*h+h/2; + + var newx = a * Math.Cos(radians) * w; + var newy = b * Math.Sin(radians) * h; + + if (first) + { + firstPoint = string.Format("{0},{1}", newx, newy); + } + pointsSb.AppendFormat("{2}{0},{1}", newx, newy, first ? "" : " "); + first = false; + } + + pointsSb.AppendFormat(" {0}", firstPoint); + } + + return ConvertTmxObjectToFrbPolygon(name, x + w / 2.0f, y + h / 2.0f, rotation, pointsSb.ToString(), true); + } + + private static Polygon ConvertTmxObjectToFrbPolygon(string name, + double x, double y, double rotation, string points, bool connectBackToStart) + { + if (string.IsNullOrEmpty(points)) + { + return null; + } + + var polygon = new Polygon(); + string[] pointString = points.Split(" ".ToCharArray()); + + polygon.Name = name; + + // Nov. 19th, 2014 - Domenic: + // I am ripping this code apart a little, because shapes really should not involve tile sizes in their x/y calculations. + // I'm not sure why this was ever done this way, as TMX gives the X/Y and width/height already. The old way was basically to convert + // the x/y coordinates into tile based coordinates and then re-convert back to full x/y coordinates. This makes no sense any more to me. + // + // Having examined TMX format a little more, it seems that the x/y position is always specified + // + //float fx = x; + //float fy = y; + + //if ("orthogonal".Equals(tiledMapSave.orientation)) + //{ + // fx -= tiledMapSave.tilewidth / 2.0f; + // fy -= tiledMapSave.tileheight + (tiledMapSave.tileheight / 2.0f); + //} + //else if ("isometric".Equals(tiledMapSave.orientation)) + //{ + // fx -= tiledMapSave.tilewidth / 4.0f; + // fy -= tiledMapSave.tileheight / 2.0f; + //} + + //tiledMapSave.CalculateWorldCoordinates( + // 0, fx / tiledMapSave.tileheight, fy / tiledMapSave.tileheight, + // tiledMapSave.tilewidth, tiledMapSave.tileheight, + // w * tiledMapSave.tilewidth, out newx, out newy, out z); + + //polygon.X = newx - tiledMapSave.tilewidth / 2.0f; + //polygon.Y = newy - tiledMapSave.tileheight / 2.0f; + //var pointsArr = new Point[pointString.Length + (connectBackToStart ? 1 : 0)]; + + var pointsList = + pointString.Select(p => + { + var xy = p.Split(",".ToCharArray()); + return new Point + { + X = Convert.ToDouble(xy[0], System.Globalization.NumberFormatInfo.InvariantInfo), + Y = -Convert.ToDouble(xy[1], System.Globalization.NumberFormatInfo.InvariantInfo) + }; + }).ToList(); + + if (connectBackToStart) + { + pointsList.Add(new Point(pointsList[0].X, pointsList[0].Y)); + } + + if (IsClockwise(pointsList) == false) + { + pointsList.Reverse(); + } + + polygon.Points = pointsList.ToArray(); + polygon.X = (float)x; + polygon.Y = (float)-y; + polygon.RotationZ = -MathHelper.ToRadians((float)rotation); + + return polygon; + } + + // From: + // https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order + private static bool IsClockwise(List pointsList) + { + double sum = 0; + for (int i = 0; i < pointsList.Count - 1; i++) + { + var point = pointsList[i]; + var pointAfter = pointsList[i + 1]; + + sum += (pointAfter.X - point.X) * (pointAfter.Y + point.Y); + } + + return sum > 0; + } + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/Tileset.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/Tileset.cs new file mode 100644 index 000000000..cd5ab9210 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/Tileset.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FlatRedBall.TileGraphics +{ + public class Tileset + { + private Texture2D mTexture; + private int mNumRows, mNumCols; + private float mTextureTileHeight, mTextureTileWidth; + private int mTileDimensionWidth, mTileDimensionHeight; // in pixels + + + + public Tileset(string pTilesetFilename, int pTileDimensionWidth, int pTileDimensionHeight) + { + mTexture = FlatRedBall.FlatRedBallServices.Load(pTilesetFilename); + + mTileDimensionWidth = pTileDimensionWidth; + mTileDimensionHeight = pTileDimensionHeight; + + mNumCols = mTexture.Width / pTileDimensionWidth; + mNumRows = mTexture.Height / pTileDimensionHeight; + + mTextureTileWidth = (float)(pTileDimensionWidth) / (float)(mTexture.Width); + mTextureTileHeight = (float)(pTileDimensionHeight) / (float)(mTexture.Height); + // FlatRedBall.Debugging.Debugger.CommandLineWrite("Dimensions: " + mTextureTileWidth + " x " + mTextureTileHeight); + } + + public Tileset(Texture2D tilesetTexture, int tileDimensionWidth, int tileDimensionHeight) + { + mTexture = tilesetTexture; + + mTileDimensionWidth = tileDimensionWidth; + mTileDimensionHeight = tileDimensionHeight; + + mNumCols = mTexture.Width / tileDimensionWidth; + mNumRows = mTexture.Height / tileDimensionHeight; + + mTextureTileWidth = (float)(tileDimensionWidth) / (float)(mTexture.Width); + mTextureTileHeight = (float)(tileDimensionHeight) / (float)(mTexture.Height); + + + } + + public short GetTextureIndexFromCoordinate(Vector2 topLeftUVCoordinate) + { + short column = (short)(topLeftUVCoordinate.X / mTextureTileWidth); + short row = (short)(topLeftUVCoordinate.Y / mTextureTileHeight); + + return (short)((mNumCols*row) + column); + } + + public Vector2[] GetTextureCoordinateVectorsOfTextureIndex(int textureId) + { + // TODO: pId needs to be constrained! + + Vector2[] coords = new Vector2[4]; + + GetTextureCoordinateVectorsOfTextureIndex(textureId, coords); + + return coords; + + } + + public void GetTextureCoordinateVectorsOfTextureIndex(int textureId, Vector2[] coords) + { + // TODO: pId needs to be constrained! + + float x, y; + if (textureId == 0) + { + x = 0; + y = 0; + } + else + { + x = (float)(textureId % mNumCols) * mTextureTileWidth; + + y = ((int)((float)textureId - x) / mNumCols) * mTextureTileHeight; + } + + + // Coords are + // 3 2 + // + // 0 1 + + GetCoordinatesForTile(coords, x, y); + } + + public void GetCoordinatesForTile(Vector2[] coords, float leftTextureCoordinate, float topTextureCoordinate) + { + + coords[0] = new Vector2(leftTextureCoordinate, topTextureCoordinate + mTextureTileHeight); + coords[1] = new Vector2(leftTextureCoordinate + mTextureTileWidth, topTextureCoordinate + mTextureTileHeight); + coords[2] = new Vector2(leftTextureCoordinate + mTextureTileWidth, topTextureCoordinate); + coords[3] = new Vector2(leftTextureCoordinate, topTextureCoordinate); + } + + + + } +} diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TilesetExtensionMethods.cs b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TilesetExtensionMethods.cs new file mode 100644 index 000000000..66cdfccab --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TileGraphics/TilesetExtensionMethods.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TMXGlueLib +{ + public static class TilesetExtensionMethods + { + public static void IndexToCoordinate(this Tileset tileset, long xIndex, long yIndex, out int xCoordinate, out int yCoordinate) + { + xCoordinate = tileset.Margin + (int)xIndex * (tileset.Tilewidth + tileset.Spacing); + yCoordinate = tileset.Margin + (int)yIndex * (tileset.Tileheight + tileset.Spacing); + + + } + + public static void CoordinateToIndex(this Tileset tileset, int xCoordinate, int yCoordinate, out int xIndex, out int yIndex) + { + xIndex = 0; + yIndex = 0; + if (tileset.Images.Length != 0) + { + // We're assuming the first image, not sure why we'd have multiple images in one tileset....or at least we won't + // supportthat yet. + var image = tileset.Images[0]; + + int effectiveImageWidth = tileset.Images[0].width; + int effectiveImageHeight = tileset.Images[0].height; + + if (xCoordinate < effectiveImageWidth && yCoordinate < effectiveImageHeight) + { + + + if (tileset.Margin != 0) + { + xCoordinate -= tileset.Margin * 2; + yCoordinate -= tileset.Margin * 2; + } + + + int effectiveTileWidth = tileset.Tilewidth + tileset.Spacing; + int effectiveTileHeight = tileset.Tileheight + tileset.Spacing; + + xIndex = xCoordinate / effectiveTileWidth; + yIndex = yCoordinate / effectiveTileHeight; + + } + + } + } + + public static int GetNumberOfTilesWide(this Tileset tileset) + { + if (tileset.Images.Length == 0 || tileset.Tilewidth == 0) + { + return 0; + } + else + { + // This is the width of the image as reported by the .tsx or .tmx, but + // it may not actually be the image's width if the .png has changed since + // the tsx/tmx was saved + int imageWidth = tileset.Images[0].width; + + return GetNumberOfTilesWide( + tileset.Images[0].width, tileset.Margin, tileset.Tilewidth, tileset.Spacing); + } + } + + + public static int GetNumberOfTilesWide(int imageWidth, int margin, int tileWidth, int spacing) + { + + if (tileWidth == 0) + { + throw new Exception("The tileWidth must not be 0"); + } + + + // The following logic + // deserves an explanation: + // Consider a simple tileset + // with 2 tiles, and a single + // spacing between them. Assume + // that the tiles are 16 pixels wide + // and that the spacing is 1 pixel wide. + // In this case, the width of the entire + // tileset image would be 33. That's (16+1+16). + // However, let's look at the following line of code + // below: + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + // In this case, the formula would be: + // int tilesWide = (33-0) / (16+1); + // Which is equivalent to: + // int tilesWide = 33 / 17; + // which is equivalent to: + // int tilesWide = 1; + // But we clearly stated above that we have two tiles (16+1+16). + // The reason for this is because each tile *except the last* will + // be considered to be wider by 1 because of its spacing value. The + // last one won't be considered wider because there's always one-less space + // than there are tiles. + // We can correct this simply by adding 1 to the resulting tilesWide *if* there + // is spacing... + // Update January 26, 2014 + // We need to multiply margin + // by 2 since the margin applies + // to all sides + // ...so let's do that here: + //int tilesWide = (imageWidth - (2 * margin)) / (tileWidth + spacing); + + //if (spacing != 0) + //{ + // tilesWide++; + //} + + // Update February 7, 2014 + // No, this doesn't seem like it's working right, and it's confusing, so I'm going + // to break this down a bit: + // First let's take off the margin so we can see how + // much usable space we have: + int usableSpace = imageWidth - 2 * margin; + // If there is a margin, all tiles except the last will have the margin added to the right of them + // Since the last one won't have the margin added, let's just add the margin to the usable space, then do + // a simple int division: + usableSpace += spacing; + + return usableSpace / (tileWidth + spacing); + + } + + public static int GetTileCount(this Tileset tileset) + { + int width = tileset.GetNumberOfTilesWide(); + int height = tileset.GetNumberOfTilesTall(); + + return width * height; + + + } + + public static int GetNumberOfTilesTall(this Tileset tileset) + { + if (tileset.Images.Length == 0) + { + return 0; + } + else + { + return GetNumberOfTilesWide( + tileset.Images[0].height, tileset.Margin, tileset.Tileheight, tileset.Spacing); + } + } + + + public static int GetNumberOfTilesTall(int imageHeight, int margin, int tileHeight, int spacing) + { + // See GetNumberOfTilesWide for a discussion about this approach + int usableSpace = imageHeight - 2 * margin; + usableSpace += spacing; + + return usableSpace / (tileHeight + spacing); + + + } + + public static int IndexToLocalId(this Tileset tileset, int xIndex, int yIndex) + { + return xIndex + yIndex * tileset.GetNumberOfTilesWide(); + + } + + public static int CoordinateToLocalId(this Tileset tileset, int xCoordinate, int yCoordinate) + { + int xIndex; + int yIndex; + + tileset.CoordinateToIndex(xCoordinate, yCoordinate, out xIndex, out yIndex); + + return tileset.IndexToLocalId(xIndex, yIndex); + + } + + } +} +; \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/TiledObjects.Generated.xml b/FRBDK/GlueView2/FlatRedBallWpf/TiledObjects.Generated.xml new file mode 100644 index 000000000..ca1145a88 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/TiledObjects.Generated.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FRBDK/GlueView2/FlatRedBallWpf/packages.config b/FRBDK/GlueView2/FlatRedBallWpf/packages.config new file mode 100644 index 000000000..63dbd9359 --- /dev/null +++ b/FRBDK/GlueView2/FlatRedBallWpf/packages.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FRBDK/GlueView2/GView2.sln b/FRBDK/GlueView2/GView2.sln new file mode 100644 index 000000000..3f7e9e122 --- /dev/null +++ b/FRBDK/GlueView2/GView2.sln @@ -0,0 +1,347 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2047 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlueView2", "FlatRedBallWpf\GlueView2.csproj", "{B44EEB5C-9155-437C-9AF2-DD6688192185}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlatRedBallXna4", "..\..\Engines\FlatRedBallXNA\FlatRedBall\FlatRedBallXna4.csproj", "{E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "FlatRedBallShared", "..\..\Engines\FlatRedBallXNA\FlatRedBall\FlatRedBallShared.shproj", "{0BB8CBE3-8503-46C1-9272-D98E153A230E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateInterpolation", "..\..\Engines\Forms\FlatRedBall.Forms\StateInterpolation\StateInterpolation.csproj", "{F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlueSaveClasses", "..\Glue\GlueSaveClasses\GlueSaveClasses.csproj", "{545FF183-4D9D-4121-9A04-1D354B0B0DBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorObjectsXna4", "..\FRBDK Supporting Projects\EditorObjects\EditorObjectsXna4.csproj", "{E1D670B6-AD42-4B84-AFF8-D568097BE03D}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\Engines\FlatRedBallXNA\FlatRedBall\FlatRedBallShared.projitems*{0bb8cbe3-8503-46c1-9272-d98e153a230e}*SharedItemsImports = 13 + ..\..\Engines\FlatRedBallXNA\FlatRedBall\FlatRedBallShared.projitems*{e1cb7d7b-e2ec-4deb-92e2-6ef0b76f40f0}*SharedItemsImports = 4 + ..\Glue\StateInterpolationPlugin\StateInterpolationPlugin\StateInterpolationShared.projitems*{f0bdee46-0374-4278-b1c3-f91a76fe3a8d}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + AutomatedBuild|Any CPU = AutomatedBuild|Any CPU + AutomatedBuild|Mixed Platforms = AutomatedBuild|Mixed Platforms + AutomatedBuild|x86 = AutomatedBuild|x86 + Debug Docs|Any CPU = Debug Docs|Any CPU + Debug Docs|Mixed Platforms = Debug Docs|Mixed Platforms + Debug Docs|x86 = Debug Docs|x86 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Debug2|Any CPU = Debug2|Any CPU + Debug2|Mixed Platforms = Debug2|Mixed Platforms + Debug2|x86 = Debug2|x86 + DebugProfile|Any CPU = DebugProfile|Any CPU + DebugProfile|Mixed Platforms = DebugProfile|Mixed Platforms + DebugProfile|x86 = DebugProfile|x86 + Profile|Any CPU = Profile|Any CPU + Profile|Mixed Platforms = Profile|Mixed Platforms + Profile|x86 = Profile|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + Submission Phone|Any CPU = Submission Phone|Any CPU + Submission Phone|Mixed Platforms = Submission Phone|Mixed Platforms + Submission Phone|x86 = Submission Phone|x86 + Submission|Any CPU = Submission|Any CPU + Submission|Mixed Platforms = Submission|Mixed Platforms + Submission|x86 = Submission|x86 + UnitTests|Any CPU = UnitTests|Any CPU + UnitTests|Mixed Platforms = UnitTests|Mixed Platforms + UnitTests|x86 = UnitTests|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|x86.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.AutomatedBuild|x86.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug Docs|Any CPU.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug Docs|Any CPU.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug Docs|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug Docs|Mixed Platforms.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug Docs|x86.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|x86.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug|x86.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|Any CPU.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|Any CPU.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|Mixed Platforms.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|x86.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Debug2|x86.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.DebugProfile|Any CPU.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.DebugProfile|Any CPU.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.DebugProfile|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.DebugProfile|Mixed Platforms.Build.0 = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.DebugProfile|x86.ActiveCfg = Debug|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Profile|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Profile|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Profile|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Profile|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Profile|x86.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Release|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Release|x86.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission Phone|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission Phone|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission Phone|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission Phone|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission Phone|x86.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.Submission|x86.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.UnitTests|Any CPU.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.UnitTests|Any CPU.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.UnitTests|Mixed Platforms.ActiveCfg = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.UnitTests|Mixed Platforms.Build.0 = Release|Any CPU + {B44EEB5C-9155-437C-9AF2-DD6688192185}.UnitTests|x86.ActiveCfg = Release|Any CPU + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.AutomatedBuild|Any CPU.ActiveCfg = AutomatedBuild|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.AutomatedBuild|Mixed Platforms.ActiveCfg = AutomatedBuild|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.AutomatedBuild|Mixed Platforms.Build.0 = AutomatedBuild|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.AutomatedBuild|x86.ActiveCfg = AutomatedBuild|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.AutomatedBuild|x86.Build.0 = AutomatedBuild|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug Docs|Any CPU.ActiveCfg = Debug Docs|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug Docs|Mixed Platforms.ActiveCfg = Debug Docs|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug Docs|Mixed Platforms.Build.0 = Debug Docs|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug Docs|x86.ActiveCfg = Debug Docs|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug Docs|x86.Build.0 = Debug Docs|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug|Any CPU.ActiveCfg = Debug|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug|x86.ActiveCfg = Debug|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug|x86.Build.0 = Debug|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug2|Any CPU.ActiveCfg = Debug2|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug2|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug2|Mixed Platforms.Build.0 = Debug2|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug2|x86.ActiveCfg = Debug2|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Debug2|x86.Build.0 = Debug2|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.DebugProfile|Any CPU.ActiveCfg = DebugProfile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.DebugProfile|Mixed Platforms.ActiveCfg = DebugProfile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.DebugProfile|Mixed Platforms.Build.0 = DebugProfile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.DebugProfile|x86.ActiveCfg = DebugProfile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.DebugProfile|x86.Build.0 = DebugProfile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Profile|Any CPU.ActiveCfg = Profile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Profile|Mixed Platforms.ActiveCfg = Profile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Profile|Mixed Platforms.Build.0 = Profile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Profile|x86.ActiveCfg = Profile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Profile|x86.Build.0 = Profile|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Release|Any CPU.ActiveCfg = Release|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Release|Mixed Platforms.Build.0 = Release|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Release|x86.ActiveCfg = Release|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Release|x86.Build.0 = Release|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission Phone|Any CPU.ActiveCfg = Submission Phone|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission Phone|Mixed Platforms.ActiveCfg = Submission Phone|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission Phone|Mixed Platforms.Build.0 = Submission Phone|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission Phone|x86.ActiveCfg = Submission Phone|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission Phone|x86.Build.0 = Submission Phone|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission|Any CPU.ActiveCfg = Submission|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission|Mixed Platforms.ActiveCfg = Submission|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission|Mixed Platforms.Build.0 = Submission|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission|x86.ActiveCfg = Submission|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.Submission|x86.Build.0 = Submission|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.UnitTests|Any CPU.ActiveCfg = UnitTests|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.UnitTests|Mixed Platforms.ActiveCfg = UnitTests|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.UnitTests|Mixed Platforms.Build.0 = UnitTests|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.UnitTests|x86.ActiveCfg = UnitTests|x86 + {E1CB7D7B-E2EC-4DEB-92E2-6EF0B76F40F0}.UnitTests|x86.Build.0 = UnitTests|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.AutomatedBuild|x86.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|Any CPU.ActiveCfg = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|Any CPU.Build.0 = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|Mixed Platforms.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|Mixed Platforms.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|x86.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug Docs|x86.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|x86.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug|x86.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|Any CPU.ActiveCfg = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|Any CPU.Build.0 = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|Mixed Platforms.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|Mixed Platforms.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|x86.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Debug2|x86.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|Any CPU.ActiveCfg = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|Any CPU.Build.0 = Debug|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|Mixed Platforms.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|Mixed Platforms.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|x86.ActiveCfg = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.DebugProfile|x86.Build.0 = Debug|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Profile|x86.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Release|x86.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission Phone|x86.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.Submission|x86.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|Any CPU.ActiveCfg = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|Any CPU.Build.0 = Release|Any CPU + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|Mixed Platforms.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|Mixed Platforms.Build.0 = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|x86.ActiveCfg = Release|x86 + {F0BDEE46-0374-4278-B1C3-F91A76FE3A8D}.UnitTests|x86.Build.0 = Release|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|Any CPU.ActiveCfg = AutomatedBuild|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|Any CPU.Build.0 = AutomatedBuild|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|Mixed Platforms.ActiveCfg = AutomatedBuild|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|Mixed Platforms.Build.0 = AutomatedBuild|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|x86.ActiveCfg = AutomatedBuild|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.AutomatedBuild|x86.Build.0 = AutomatedBuild|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug Docs|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|x86.ActiveCfg = Debug|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug|x86.Build.0 = Debug|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Debug2|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.DebugProfile|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Profile|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|Any CPU.Build.0 = Release|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|Mixed Platforms.Build.0 = Release|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|x86.ActiveCfg = Release|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Release|x86.Build.0 = Release|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission Phone|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.Submission|x86.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|Any CPU.ActiveCfg = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|Any CPU.Build.0 = Debug2|Any CPU + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|Mixed Platforms.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|Mixed Platforms.Build.0 = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|x86.ActiveCfg = Debug2|x86 + {545FF183-4D9D-4121-9A04-1D354B0B0DBA}.UnitTests|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|Any CPU.ActiveCfg = AutomatedBuild|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|Any CPU.Build.0 = AutomatedBuild|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|Mixed Platforms.ActiveCfg = AutomatedBuild|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|Mixed Platforms.Build.0 = AutomatedBuild|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|x86.ActiveCfg = AutomatedBuild|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.AutomatedBuild|x86.Build.0 = AutomatedBuild|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug Docs|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|x86.ActiveCfg = Debug|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug|x86.Build.0 = Debug|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Debug2|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.DebugProfile|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Profile|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|Any CPU.Build.0 = Release|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|Mixed Platforms.Build.0 = Release|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|x86.ActiveCfg = Release|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Release|x86.Build.0 = Release|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission Phone|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|Any CPU.ActiveCfg = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|Any CPU.Build.0 = Debug2|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|Mixed Platforms.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|Mixed Platforms.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|x86.ActiveCfg = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.Submission|x86.Build.0 = Debug2|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|Any CPU.ActiveCfg = UnitTests|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|Any CPU.Build.0 = UnitTests|Any CPU + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|Mixed Platforms.ActiveCfg = UnitTests|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|Mixed Platforms.Build.0 = UnitTests|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|x86.ActiveCfg = UnitTests|x86 + {E1D670B6-AD42-4B84-AFF8-D568097BE03D}.UnitTests|x86.Build.0 = UnitTests|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9A90210A-5E54-40D3-8F38-EEE3A4C5650A} + EndGlobalSection +EndGlobal diff --git a/FRBDK/GlueView2Test/GlueView2.sln b/FRBDK/GlueView2Test/GlueView2.sln new file mode 100644 index 000000000..dfde58617 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2047 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlueView2", "GlueView2\GlueView2.csproj", "{864CF0A3-0500-4FFE-975B-95E086DDB688}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {864CF0A3-0500-4FFE-975B-95E086DDB688}.Debug|x86.ActiveCfg = Debug|x86 + {864CF0A3-0500-4FFE-975B-95E086DDB688}.Debug|x86.Build.0 = Debug|x86 + {864CF0A3-0500-4FFE-975B-95E086DDB688}.Release|x86.ActiveCfg = Release|x86 + {864CF0A3-0500-4FFE-975B-95E086DDB688}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DC92039F-E199-4982-AF0C-0F1B07896A30} + EndGlobalSection +EndGlobal diff --git a/FRBDK/GlueView2Test/GlueView2/Content/Content.mgcb b/FRBDK/GlueView2Test/GlueView2/Content/Content.mgcb new file mode 100644 index 000000000..5c2e4fca3 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:HiDef +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + diff --git a/FRBDK/GlueView2Test/GlueView2/Content/Shader.fx b/FRBDK/GlueView2Test/GlueView2/Content/Shader.fx new file mode 100644 index 000000000..a7f964eab --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Content/Shader.fx @@ -0,0 +1,243 @@ +float4x4 ViewProj : VIEWPROJ; //our world view projection matrix +uniform extern texture CurrentTexture; + + +sampler textureSampler = sampler_state +{ + Texture = ; + mipfilter = LINEAR; +}; + + +//application to vertex structure +struct a2v +{ + float4 position : POSITION0; + float4 color : COLOR0; + float4 texCoord : TEXCOORD0; + +}; + +//vertex to pixel processing structure +struct v2p +{ + float4 position : POSITION0; + float4 color : COLOR0; + float4 texCoord : TEXCOORD0; +}; +//VERTEX SHADER +void vs( in a2v IN, out v2p OUT ) +{ + //transforming our position from object space to screen space. + OUT.position = mul(IN.position, ViewProj); + OUT.color = IN.color; + OUT.texCoord = IN.texCoord; +} + +float4 TexturePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + + + // premult requires this + + color[0] = color[0] * IN.color[3]; + color[1] = color[1] * IN.color[3]; + color[2] = color[2] * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 AddPixelShader(a2v IN ) : COLOR +{ + float4 fromTexture = tex2D(textureSampler, IN.texCoord).rgba; + + fromTexture[0] = (fromTexture[0] + IN.color[0] * fromTexture[3]) * IN.color[3]; + fromTexture[1] = (fromTexture[1] + IN.color[1] * fromTexture[3]) * IN.color[3]; + fromTexture[2] = (fromTexture[2] + IN.color[2] * fromTexture[3]) * IN.color[3]; + fromTexture[3] = fromTexture[3] * IN.color[3]; + clip(fromTexture[3] - .001); + return fromTexture; +} + +float4 SubtractPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (color[0] - IN.color[0]) * IN.color[3]; + color[1] = (color[1] - IN.color[1]) * IN.color[3]; + color[2] = (color[2] - IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 ModulatePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (color[0] * IN.color[0]) * IN.color[3]; + color[1] = (color[1] * IN.color[1]) * IN.color[3]; + color[2] = (color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 Modulate2XPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (2 * color[0] * IN.color[0]) * IN.color[3]; + color[1] = (2 * color[1] * IN.color[1]) * IN.color[3]; + color[2] = (2 * color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 Modulate4XPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (4 * color[0] * IN.color[0]) * IN.color[3]; + color[1] = (4 * color[1] * IN.color[1]) * IN.color[3]; + color[2] = (4 * color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 InversePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (1 - color[0]) * IN.color[3]; + color[1] = (1 - color[1]) * IN.color[3]; + color[2] = (1 - color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 ColorPixelShader(a2v IN ) : COLOR +{ + clip(IN.color[3] - .001); + return IN.color; +} + +float4 ColorTextureAlphaPixelShader(a2v IN ) : COLOR +{ + float4 color = IN.color; + float alphaFromTexture = tex2D(textureSampler, IN.texCoord).a; + color[0] = color[0] * IN.color[3] * alphaFromTexture; + + color[1] = color[1] * IN.color[3] * alphaFromTexture; + + color[2] = color[2] * IN.color[3] * alphaFromTexture; + + color[3] = color[3] * alphaFromTexture; + clip(color[3] - .001); + return color; +} + +float4 InterpolateColorPixelShader(a2v IN ) :COLOR +{ + float4 color = IN.color; + + color = (color[3] * tex2D(textureSampler, IN.texCoord)) + (1 - color[3])*(color); + + color[3] = tex2D(textureSampler, IN.texCoord).a; + clip(color[3] - .001); + //color[3] = color[3] * tex2D(textureSampler, IN.texCoord).a; + return color; + +} + +technique Texture +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 TexturePixelShader(); + } +} + +technique Add +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 AddPixelShader(); + } +} + +technique Subtract +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 SubtractPixelShader(); + } +} + +technique Modulate +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ModulatePixelShader(); + } +} + +technique Modulate2X +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 Modulate2XPixelShader(); + } +} + +technique Modulate4X +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 Modulate4XPixelShader(); + } +} + +technique InverseTexture +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 InversePixelShader(); + } +} + +technique Color +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ColorPixelShader(); + } +} + +technique ColorTextureAlpha +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ColorTextureAlphaPixelShader(); + } +} + +technique InterpolateColor +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 InterpolateColorPixelShader(); + } + +} + diff --git a/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.fx b/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.fx new file mode 100644 index 000000000..f68482104 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.fx @@ -0,0 +1,243 @@ +float4x4 ViewProj : VIEWPROJ; //our world view projection matrix +uniform extern texture CurrentTexture; + + +sampler textureSampler = sampler_state +{ + Texture = ; + mipfilter = POINT; +}; + + +//application to vertex structure +struct a2v +{ + float4 position : POSITION0; + float4 color : COLOR0; + float4 texCoord : TEXCOORD0; + +}; + +//vertex to pixel processing structure +struct v2p +{ + float4 position : POSITION0; + float4 color : COLOR0; + float4 texCoord : TEXCOORD0; +}; +//VERTEX SHADER +void vs( in a2v IN, out v2p OUT ) +{ + //transforming our position from object space to screen space. + OUT.position = mul(IN.position, ViewProj); + OUT.color = IN.color; + OUT.texCoord = IN.texCoord; +} + +float4 TexturePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + + + // premult requires this + + color[0] = color[0] * IN.color[3]; + color[1] = color[1] * IN.color[3]; + color[2] = color[2] * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 AddPixelShader(a2v IN ) : COLOR +{ + float4 fromTexture = tex2D(textureSampler, IN.texCoord).rgba; + + fromTexture[0] = (fromTexture[0] + IN.color[0] * fromTexture[3]) * IN.color[3]; + fromTexture[1] = (fromTexture[1] + IN.color[1] * fromTexture[3]) * IN.color[3]; + fromTexture[2] = (fromTexture[2] + IN.color[2] * fromTexture[3]) * IN.color[3]; + fromTexture[3] = fromTexture[3] * IN.color[3]; + clip(fromTexture[3] - .001); + return fromTexture; +} + +float4 SubtractPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (color[0] - IN.color[0]) * IN.color[3]; + color[1] = (color[1] - IN.color[1]) * IN.color[3]; + color[2] = (color[2] - IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 ModulatePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (color[0] * IN.color[0]) * IN.color[3]; + color[1] = (color[1] * IN.color[1]) * IN.color[3]; + color[2] = (color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 Modulate2XPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (2 * color[0] * IN.color[0]) * IN.color[3]; + color[1] = (2 * color[1] * IN.color[1]) * IN.color[3]; + color[2] = (2 * color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 Modulate4XPixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (4 * color[0] * IN.color[0]) * IN.color[3]; + color[1] = (4 * color[1] * IN.color[1]) * IN.color[3]; + color[2] = (4 * color[2] * IN.color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 InversePixelShader(a2v IN ) : COLOR +{ + float4 color = tex2D(textureSampler, IN.texCoord).rgba; + color[0] = (1 - color[0]) * IN.color[3]; + color[1] = (1 - color[1]) * IN.color[3]; + color[2] = (1 - color[2]) * IN.color[3]; + color[3] = color[3] * IN.color[3]; + clip(color[3] - .001); + return color; +} + +float4 ColorPixelShader(a2v IN ) : COLOR +{ + clip(IN.color[3] - .001); + return IN.color; +} + +float4 ColorTextureAlphaPixelShader(a2v IN ) : COLOR +{ + float4 color = IN.color; + float alphaFromTexture = tex2D(textureSampler, IN.texCoord).a; + color[0] = color[0] * IN.color[3] * alphaFromTexture; + + color[1] = color[1] * IN.color[3] * alphaFromTexture; + + color[2] = color[2] * IN.color[3] * alphaFromTexture; + + color[3] = color[3] * alphaFromTexture; + clip(color[3] - .001); + return color; +} + +float4 InterpolateColorPixelShader(a2v IN ) :COLOR +{ + float4 color = IN.color; + + color = (color[3] * tex2D(textureSampler, IN.texCoord)) + (1 - color[3])*(color); + + color[3] = tex2D(textureSampler, IN.texCoord).a; + clip(color[3] - .001); + //color[3] = color[3] * tex2D(textureSampler, IN.texCoord).a; + return color; + +} + +technique Texture +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 TexturePixelShader(); + } +} + +technique Add +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 AddPixelShader(); + } +} + +technique Subtract +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 SubtractPixelShader(); + } +} + +technique Modulate +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ModulatePixelShader(); + } +} + +technique Modulate2X +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 Modulate2XPixelShader(); + } +} + +technique Modulate4X +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 Modulate4XPixelShader(); + } +} + +technique InverseTexture +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 InversePixelShader(); + } +} + +technique Color +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ColorPixelShader(); + } +} + +technique ColorTextureAlpha +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 ColorTextureAlphaPixelShader(); + } +} + +technique InterpolateColor +{ + pass p0 + { + vertexshader = compile vs_1_1 vs(); + pixelshader = compile ps_2_0 InterpolateColorPixelShader(); + } + +} + diff --git a/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.xnb b/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.xnb new file mode 100644 index 000000000..6416df772 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Content/ShaderForcePoint.xnb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Content/TiledObjects.Generated.xml b/FRBDK/GlueView2Test/GlueView2/Content/TiledObjects.Generated.xml new file mode 100644 index 000000000..ca1145a88 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Content/TiledObjects.Generated.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/Content/shader.xnb b/FRBDK/GlueView2Test/GlueView2/Content/shader.xnb new file mode 100644 index 000000000..08d40e6bc Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Content/shader.xnb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Game1.cs b/FRBDK/GlueView2Test/GlueView2/Game1.cs new file mode 100644 index 000000000..3f138c317 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Game1.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +using FlatRedBall; +using FlatRedBall.Graphics; +using FlatRedBall.Screens; +using Microsoft.Xna.Framework; + +using System.Linq; + +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace GlueView2 +{ + public class Game1 : Microsoft.Xna.Framework.Game + { + GraphicsDeviceManager graphics; + + public Game1() : base() + { + graphics = new GraphicsDeviceManager(this); + +#if WINDOWS_PHONE || ANDROID || IOS + + // Frame rate is 30 fps by default for Windows Phone, + // so let's keep that for other phones too + TargetElapsedTime = TimeSpan.FromTicks(333333); + graphics.IsFullScreen = true; +#elif WINDOWS || DESKTOP_GL + graphics.PreferredBackBufferWidth = 800; + graphics.PreferredBackBufferHeight = 600; +#endif + + +#if WINDOWS_8 + FlatRedBall.Instructions.Reflection.PropertyValuePair.TopLevelAssembly = + this.GetType().GetTypeInfo().Assembly; +#endif + + } + + protected override void Initialize() + { + #if IOS + var bounds = UIKit.UIScreen.MainScreen.Bounds; + var nativeScale = UIKit.UIScreen.MainScreen.Scale; + var screenWidth = (int)(bounds.Width * nativeScale); + var screenHeight = (int)(bounds.Height * nativeScale); + graphics.PreferredBackBufferWidth = screenWidth; + graphics.PreferredBackBufferHeight = screenHeight; + #endif + + FlatRedBallServices.InitializeFlatRedBall(this, graphics); + + CameraSetup.SetupCamera(SpriteManager.Camera, graphics); + GlobalContent.Initialize(); + FlatRedBall.Screens.ScreenManager.Start(typeof(GlueView2.Screens.MainScreen)); + + base.Initialize(); + } + + + protected override void Update(GameTime gameTime) + { + FlatRedBallServices.Update(gameTime); + + FlatRedBall.Screens.ScreenManager.Activity(); + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + FlatRedBallServices.Draw(); + + base.Draw(gameTime); + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/GlueSettings/BuildToolAssociation.xml b/FRBDK/GlueView2Test/GlueView2/GlueSettings/BuildToolAssociation.xml new file mode 100644 index 000000000..463e0a139 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/GlueSettings/BuildToolAssociation.xml @@ -0,0 +1,14 @@ + + + + + %Glue%Libraries\BMFont\bmfont.exe + false + bmfc + fnt + true + -c + -o + + + \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/GlueView2.csproj b/FRBDK/GlueView2Test/GlueView2/GlueView2.csproj new file mode 100644 index 000000000..c6c295bbc --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/GlueView2.csproj @@ -0,0 +1,327 @@ + + + + + Debug + x86 + 8.0.30703 + 2.0 + {864CF0A3-0500-4FFE-975B-95E086DDB688} + WinExe + Properties + GlueView2 + GlueView2 + 512 + DesktopGL + v4.7.1 + + + + x86 + true + full + false + bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ + TRACE;DEBUG;MONOGAME; DESKTOP_GL; XNA4; FRB_XNA + prompt + 4 + + + x86 + pdbonly + true + bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ + TRACE;MONOGAME; DESKTOP_GL; XNA4; FRB_XNA + prompt + 4 + + + Icon.ico + + + + + + + + + + + + + MainScreen.cs + + + + + + + + + + + + + + + + + + + + + + + + ReducedTileMapInfo.cs + + + + + + + + + + + + + ..\packages\CS-Script.bin.3.28.7\lib\net46\CSScriptLibrary.dll + + + False + Libraries\DesktopGl\Debug\FlatRedBall.Forms.dll + + + False + Libraries\DesktopGl\Debug\FlatRedBallDesktopGL.dll + + + False + Libraries\DesktopGl\Debug\GumCoreXnaPc.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + + ..\packages\CS-Script.bin.3.28.7\lib\net46\Mono.CSharp.dll + + + False + Libraries\DesktopGl\Debug\MonoGame.Framework.dll + + + False + Libraries\DesktopGl\Debug\NVorbis.dll + + + False + Libraries\DesktopGl\Debug\OpenTK.dll + + + False + Libraries\DesktopGl\Debug\StateInterpolation.dll + + + + ..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + True + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + True + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + True + + + ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + True + + + + ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + + + ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + True + + + ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + True + + + ..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + True + + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + True + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + True + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + True + + + + + PreserveNewest + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/Icon.ico b/FRBDK/GlueView2Test/GlueView2/Icon.ico new file mode 100644 index 000000000..7d9dec187 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Icon.ico differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.dll new file mode 100644 index 000000000..df2d86869 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.pdb new file mode 100644 index 000000000..da6e1d8cf Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBall.Forms.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.dll new file mode 100644 index 000000000..46a93dcd3 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.pdb new file mode 100644 index 000000000..c414066b2 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/FlatRedBallDesktopGL.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.dll new file mode 100644 index 000000000..d95325086 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.pdb new file mode 100644 index 000000000..00e624fc9 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/GumCoreXnaPc.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/Lidgren.Network.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/Lidgren.Network.dll new file mode 100644 index 000000000..9d7855dd3 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/Lidgren.Network.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.Net.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.Net.dll new file mode 100644 index 000000000..1565e3ab6 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.Net.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.dll new file mode 100644 index 000000000..5ca2bc7e0 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/MonoGame.Framework.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/NVorbis.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/NVorbis.dll new file mode 100644 index 000000000..282aca69e Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/NVorbis.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll new file mode 100644 index 000000000..ac0d46823 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll.config b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll.config new file mode 100644 index 000000000..7098d39e9 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.dll new file mode 100644 index 000000000..f7d0bd89f Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.pdb new file mode 100644 index 000000000..4458a4ee3 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Debug/StateInterpolation.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.dll new file mode 100644 index 000000000..db601c83e Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.pdb new file mode 100644 index 000000000..e4a5a9b65 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBall.Forms.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.dll new file mode 100644 index 000000000..371c4e87c Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.pdb new file mode 100644 index 000000000..a6e993d23 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/FlatRedBallDesktopGL.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.dll new file mode 100644 index 000000000..6774e70a2 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.pdb new file mode 100644 index 000000000..dd5207b34 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/GumCoreXnaPc.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/Lidgren.Network.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/Lidgren.Network.dll new file mode 100644 index 000000000..9d7855dd3 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/Lidgren.Network.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.Net.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.Net.dll new file mode 100644 index 000000000..08b101f3b Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.Net.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.dll new file mode 100644 index 000000000..5ca2bc7e0 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/MonoGame.Framework.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/NVorbis.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/NVorbis.dll new file mode 100644 index 000000000..282aca69e Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/NVorbis.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/OpenTK.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/OpenTK.dll new file mode 100644 index 000000000..ac0d46823 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/OpenTK.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.dll new file mode 100644 index 000000000..e9bbc3d90 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.pdb new file mode 100644 index 000000000..df2cc2a9b Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/DesktopGl/Release/StateInterpolation.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/FlatRedBall.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/FlatRedBall.dll new file mode 100644 index 000000000..b1b9a245a Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/FlatRedBall.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.dll b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.dll new file mode 100644 index 000000000..c9358688b Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.pdb new file mode 100644 index 000000000..5db778a48 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TMXGlueLib.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.exe b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.exe new file mode 100644 index 000000000..27cfd98bd Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.exe differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.pdb new file mode 100644 index 000000000..6b5d9a0bf Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToCSV.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.exe b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.exe new file mode 100644 index 000000000..6c4ed2471 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.exe differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.pdb new file mode 100644 index 000000000..1251172dc Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToNntx.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.exe b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.exe new file mode 100644 index 000000000..e320f5644 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.exe differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.pdb new file mode 100644 index 000000000..aa9d89330 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToScnx.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.exe b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.exe new file mode 100644 index 000000000..64aba89ba Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.exe differ diff --git a/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.pdb b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.pdb new file mode 100644 index 000000000..d80f04e05 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/Libraries/Tmx/TmxToShcx.pdb differ diff --git a/FRBDK/GlueView2Test/GlueView2/MonoGame.Framework.dll.config b/FRBDK/GlueView2Test/GlueView2/MonoGame.Framework.dll.config new file mode 100644 index 000000000..09710ae46 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/MonoGame.Framework.dll.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/FRBDK/GlueView2Test/GlueView2/OpenTK.dll.config b/FRBDK/GlueView2Test/GlueView2/OpenTK.dll.config new file mode 100644 index 000000000..3f888ccf1 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FRBDK/GlueView2Test/GlueView2/Program.cs b/FRBDK/GlueView2Test/GlueView2/Program.cs new file mode 100644 index 000000000..4c5401cc7 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Program.cs @@ -0,0 +1,20 @@ +using System; + +namespace GlueView2 +{ + /// + /// The main class. + /// + public static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + using (var game = new Game1()) + game.Run(); + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/Properties/AssemblyInfo.cs b/FRBDK/GlueView2Test/GlueView2/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d29cdf3f1 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GlueView2")] +[assembly: AssemblyProduct("GlueView2")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5e2c8698-894d-4594-8789-e75113aec277")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/FRBDK/GlueView2Test/GlueView2/Screens/MainScreen.cs b/FRBDK/GlueView2Test/GlueView2/Screens/MainScreen.cs new file mode 100644 index 000000000..9b0fd6db3 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Screens/MainScreen.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +using FlatRedBall; +using FlatRedBall.Input; +using FlatRedBall.Instructions; +using FlatRedBall.AI.Pathfinding; +using FlatRedBall.Graphics.Animation; +using FlatRedBall.Graphics.Particle; +using FlatRedBall.Math.Geometry; +using FlatRedBall.Localization; +using GlueView2.ScriptLoading; +using FlatRedBall.Screens; + +namespace GlueView2.Screens +{ + public partial class MainScreen + { + ScriptLoadingLogic scriptLoadingLogic; + Screen dynamicallyLoadedScreen; + + void CustomInitialize() + { + scriptLoadingLogic = new ScriptLoadingLogic(); + + } + + void CustomActivity(bool firstTimeCalled) + { + if (InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.Space)) + { + if(dynamicallyLoadedScreen != null) + { + dynamicallyLoadedScreen.Destroy(); + } + + + var directory = + @"C:\Users\Victor\Documents\FlatRedBallProjects\GViewTargetProject\GViewTargetProject\"; + var assembly = scriptLoadingLogic.LoadProjectCode(directory); + + FlatRedBall.IO.FileManager.RelativeDirectory = directory; + dynamicallyLoadedScreen = (Screen)assembly.CreateObject("GViewTargetProject.Screens.GameScreen"); + + + dynamicallyLoadedScreen.Initialize(true); + } + + + + } + + void CustomDestroy() + { + + + } + + static void CustomLoadStaticContent(string contentManagerName) + { + + + } + + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/ScriptLoading/ScriptLoadingLogic.cs b/FRBDK/GlueView2Test/GlueView2/ScriptLoading/ScriptLoadingLogic.cs new file mode 100644 index 000000000..07e201fee --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/ScriptLoading/ScriptLoadingLogic.cs @@ -0,0 +1,125 @@ +using CSScriptLibrary; +using FlatRedBall.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace GlueView2.ScriptLoading +{ + public class ScriptLoadingLogic + { + public Assembly LoadProjectCode(string projectDirectoryFullPath) + { + + List filesToCompile = new List(); + + // preload this: + var tweenerManager = typeof(global::StateInterpolationPlugin.TweenerManager); + + AddCameraSetup(projectDirectoryFullPath, filesToCompile); + + AddScreensAndEntities(projectDirectoryFullPath, filesToCompile); + + var assembly = CSScript.LoadFiles(filesToCompile.ToArray()); + return assembly; + } + + private void AddScreensAndEntities(string projectDirectoryFullPath, List filesToCompile) + { + string projectName = FileManager.RemovePath(projectDirectoryFullPath); + // remove trailing slash + projectName = projectName.Substring(0, projectName.Length - 1); + + + var screenDirectory = projectDirectoryFullPath + @"Screens\"; + var allScreenFiles = System.IO.Directory.GetFiles(screenDirectory) + .Where(item =>item.Contains(".Generated.")) + .ToArray(); + filesToCompile.AddRange(allScreenFiles); + + var entitiesDirectory = projectDirectoryFullPath + @"Entities\"; + var allEntityFiles = System.IO.Directory.GetFiles(entitiesDirectory) + .Where(item =>item.Contains(".Generated.")) + .ToArray(); + + filesToCompile.AddRange(allEntityFiles); + + var targetFile = AddEmptyCustomCodeFor(allScreenFiles, allEntityFiles, projectName); + filesToCompile.Add(targetFile); + } + + private string AddEmptyCustomCodeFor(string[] allScreenFiles, string[] allEntityFiles, string projectName) + { + var stringBuilder = new StringBuilder(); + + foreach(var screenFile in allScreenFiles) + { + var fileName = + FileManager.RemovePath(FileManager.RemoveExtension(FileManager.RemoveExtension(screenFile))); + + string emptyScreenContents = CreateEmptyPartialForScreen(projectName, fileName); + + stringBuilder.AppendLine(emptyScreenContents); + } + + foreach(var entityFile in allEntityFiles) + { + var fileName = + FileManager.RemovePath(FileManager.RemoveExtension(FileManager.RemoveExtension(entityFile))); + + string emptyEntityContents = CreateEmptyPartialForEntity(projectName, fileName); + + stringBuilder.AppendLine(emptyEntityContents); + } + + var targetfile = "tempEmptyPartials.cs"; + + System.IO.File.WriteAllText(targetfile, stringBuilder.ToString()); + + return targetfile; + } + + private static void AddCameraSetup(string projectDirectoryFullPath, List filesToCompile) + { + var cameraSetup = projectDirectoryFullPath + @"Setup\CameraSetup.cs"; + // load the camera setup first: + filesToCompile.Add(cameraSetup); + } + + private string CreateEmptyPartialForScreen(string projectName, string screenName) + { + return $@" +namespace {projectName}.Screens +{{ + public partial class {screenName} + {{ + void CustomInitialize(){{}} + void CustomActivity(bool firstTimeCalled){{}} + void CustomDestroy(){{}} + static void CustomLoadStaticContent(string contentManagerName){{}} + }} +}} +"; + } + + private string CreateEmptyPartialForEntity(string projectName, string entityName) + { + return $@" +namespace {projectName}.Entities +{{ + public partial class {entityName} + {{ + void CustomInitialize(){{}} + void CustomActivity(){{}} + void CustomDestroy(){{}} + static void CustomLoadStaticContent(string contentManagerName){{}} + }} +}} +"; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/Scripting.Extensions.cs b/FRBDK/GlueView2Test/GlueView2/Scripting.Extensions.cs new file mode 100644 index 000000000..070536442 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Scripting.Extensions.cs @@ -0,0 +1,295 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Remoting.Lifetime; +using System.Threading; +using System.Threading.Tasks; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios requiring asynchronous script execution as well as unloading the +// scripts being executed. +// AsyncSamples +// Samples demonstrate the use of Async and Await mechanism available in C# 5 and higher. Note that the async method extensions +// cover the complete set of CSScript.Evaluator methods. +// +// UnloadingSamples +// Samples demonstrate the use of temporary AppDoamain for loading and executing dynamic C# code (script). It is the +// only mechanism available for unloading dynamically loaded assemblies. This is a well known CLR design limitation that leads to +// memory leaks if the assembly/script loaded in the caller AppDomain. The problem affects all C# script engines (e.g. Roslyn, CodeDom) +// and it cannot be solved by the engine itself thus CS-Script provides a work around in form of the MethodExtensions for the +// CSScript.Evaluator methods that are compatible with the unloading mechanism. +// +// Nevertheless you should try to avoid using remote AppDoamain unless you have to. It is very heavy and also imposes the serialization +// constrains. +// +// All samples rely on the compiler agnostic CSScript.Evaluator API. +namespace CSScriptEvaluatorExtensions +{ + public class HostApp + { + public static void Test() + { + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing asynchronous API"); + Console.WriteLine("---------------------------------------------"); + new AsyncSamples().RunAll(); + Thread.Sleep(2000); + Console.WriteLine("\nPress 'Enter' to run uloading samples..."); + Console.ReadLine(); + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing unloading API"); + Console.WriteLine("---------------------------------------------"); + new UnloadingSamples().RunAll(); + } + + class AsyncSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name); }; + + run(LoadDelegateAsync, "Start of " + nameof(LoadDelegateAsync)); + run(LoadMethodAsync, "Start of " + nameof(LoadMethodAsync)); + run(LoadCodeAsync, "Start of " + nameof(LoadCodeAsync)); + run(CreateDelegateAsync, "Start of " + nameof(CreateDelegateAsync)); + run(CompileCodeAsync, "Start of " + nameof(CompileCodeAsync)); + run(RemoteAsynch, "Start of " + nameof(RemoteAsynch)); + } + + async void LoadDelegateAsync() + { + var product = await CSScript.Evaluator + .LoadDelegateAsync>( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadDelegateAsync), product(4, 2)); + } + + public async void LoadMethodAsync() + { + dynamic script = await CSScript.Evaluator + .LoadMethodAsync(@"public int Sum(int a, int b) + { + return a + b; + } + public int Div(int a, int b) + { + return a/b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadMethodAsync), script.Div(15, 3)); + } + + public async void LoadCodeAsync() + { + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = await CSScript.Evaluator + .LoadCodeAsync( + @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadCodeAsync), calc.Sum(1, 2)); + } + + public async void CreateDelegateAsync() + { + var product = await CSScript.Evaluator + .CreateDelegateAsync( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(CreateDelegateAsync), product(15, 3)); + } + + public async void CompileCodeAsync() + { + Assembly script = await CSScript.Evaluator + .CompileCodeAsync(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + dynamic calc = script.CreateObject("*"); + + Console.WriteLine(" End of {0}: {1}", nameof(CompileCodeAsync), calc.Sum(15, 3)); + } + + public async void RemoteAsynch() + { + var sum = await Task.Run(() => + CSScript.Evaluator + .CreateDelegateRemotely( + @"int Sum(int a, int b) + { + return a+b; + }") + ); + Console.WriteLine(" End of {0}: {1}", nameof(RemoteAsynch), sum(1, 2)); + + sum.UnloadOwnerDomain(); + } + } + + class UnloadingSamples + { + public void RunAll() + { + CreateDelegateRemotely(); + LoadMethodRemotely(); + LoadCodeRemotely(); + LoadCodeRemotelyWithInterface(); + } + + public void CreateDelegateRemotely() + { + var sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + Console.WriteLine("{0}: {1}", nameof(CreateDelegateRemotely), sum(15, 3)); + + sum.UnloadOwnerDomain(); + } + + public void LoadCodeRemotely() + { + // Class Calc doesn't implement ICals interface. Thus the compiled object cannot be typecasted into + // the interface and Evaluator will emit duck-typed assembly instead. + // But Mono and Roslyn build file-less assemblies, meaning that they cannot be used to build + // duck-typed proxies and CodeDomEvaluator needs to be used explicitly. + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.CodeDomEvaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject + { + object t; + public int Sum(int a, int b) + { + t = new Test(); + return a+b; + } + } + + class Test + { + ~Test() + { + Console.WriteLine(""Domain is unloaded: ~Test()""); + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadCodeRemotelyWithInterface() + { + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + var script = CSScript.Evaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject, CSScriptEvaluatorExtensions.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotelyWithInterface), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadMethodRemotely() + { + // LoadMethodRemotely is essentially the same as LoadCodeRemotely. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.Evaluator + .LoadMethodRemotely( + @"public int Sum(int a, int b) + { + return a+b; + } + public int Sub(int a, int b) + { + return a-b; + }"); + + Console.WriteLine("{0}: {1}", nameof(LoadMethodRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + MethodDelegate sum; + ClientSponsor sumSponsor; + + public void KeepRemoteObjectAlive() + { + sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + //Normally remote objects are disposed if they are not accessed withing a default timeout period. + //It is not even enough to keep transparent proxies or their wrappers (e.g. 'sum') referenced. + //To prevent GC collection in the remote domain use .NET ClientSponsor mechanism as below. + sumSponsor = sum.ExtendLifeFromMinutes(30); + } + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public interface IFullCalc + { + int Sum(int a, int b); + + int Sub(int a, int b); + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/Scripting.evaluator.cs b/FRBDK/GlueView2Test/GlueView2/Scripting.evaluator.cs new file mode 100644 index 000000000..0e195776f --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Scripting.evaluator.cs @@ -0,0 +1,345 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Evaluator interface (API). +// This API is a unified generic interface allowing dynamic switch of the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// Apart from Evaluator (compiler agnostic) API CS-Script offers alternative hosting model: CS-Script Native, +// which relies solely on CodeDom compiler. CS-Script Native offers some features that are not available with CS-Script Evaluator +// (e.g. script unloading). +// +// The choice of the underlying compiling engine (e.g. Mono vs CodeDom) when using CS-Script Evaluator is always dictated by the +// specifics of the hosting scenario. Thanks to in-process compiler hosting, Mono and Roslyn demonstrate much better compiling +// performance comparing to CodeDom engine. However they don't allow script debugging and caching easily supported with CodeDom. +// Mono and particularly Roslyn also leas create more memory pressure due to the higher volume of the temp assemblies loaded into +// the hosting AppDomain. Roslyn (at least CSharp.Scripting-v1.2.0.0) also has very high initial loading overhead up to 4 seconds. +// +// One of the possible approaches would be to use EvaluatorEngine.CodeDom during the active development and later on switch to Mono/Roslyn. + +namespace CSScriptEvaluatorApi +{ + public class HostApp + { + public static void Test() + { + // Just in case clear AlternativeCompiler so it is not set to Roslyn or anything else by + // the CS-Script installed (if any) on the host OS + CSScript.GlobalSettings.UseAlternativeCompiler = null; + + var samples = new EvaluatorSamples(); + + Console.WriteLine("Testing compiling services"); + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + + samples.RunAll(); + + //samples.DebugTest(); //uncomment if want to fire an assertion during the script execution + + //Profile(); //uncomment if want to test performance of the engines + } + + class EvaluatorSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name + " - OK"); }; + + run(CompileMethod_Instance, nameof(CompileMethod_Instance)); + run(CompileMethod_Static, nameof(CompileMethod_Static)); + run(CreateDelegate, nameof(CreateDelegate)); + run(LoadDelegate, nameof(LoadDelegate)); + run(LoadCode, nameof(LoadCode)); + run(LoadMethod, nameof(LoadMethod)); + run(LoadMethodWithInterface, nameof(LoadMethodWithInterface)); + run(LoadCode_WithInterface, nameof(LoadCode_WithInterface)); + run(LoadCode_WithDuckTypedInterface, nameof(LoadCode_WithDuckTypedInterface)); + } + + public void CompileMethod_Instance() + { + // 1- CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.Evaluator + .CompileMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public void CompileMethod_Static() + { + // 1 - CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - GetStaticMethod returns duck-typed delegate that accepts 'params object[]' arguments + // Note: GetStaticMethodWithArgs can be replaced with a more convenient/shorter version + // that takes the object instead of the Type and then queries objects type internally: + // "GetStaticMethod("*.Test", data)" + + var test = CSScript.Evaluator + .CompileMethod(@"using CSScriptEvaluatorApi; + static void Test(InputData data) + { + data.Index = GetIndex(); + } + static int GetIndex() + { + return Environment.TickCount; + }") + .GetStaticMethodWithArgs("*.Test", typeof(InputData)); + + var data = new InputData(); + test(data); + } + + public void CreateDelegate() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sqr = CSScript.Evaluator + .CreateDelegate(@"int Sqr(int a) + { + return a * a; + }"); + + var r = sqr(3); + } + + public void LoadDelegate() + { + // Wraps method into a class definition, loads the compiled assembly + // and returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + var product = CSScript.Evaluator + .LoadDelegate>( + @"int Product(int a, int b) + { + return a * b; + }"); + + int result = product(3, 2); + } + + public void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethod() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. + // 'public' is optional as it will be injected if the code doesn't start with it. + dynamic script = CSScript.Evaluator + .LoadMethod(@"using System; + public int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethodWithInterface() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.Evaluator + .LoadMethod( + @"int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2 - The script class implements host app interface so the returned object can be type casted into it + + var script = (ICalc)CSScript.Evaluator + .LoadCode(@"using System; + public class Script : CSScriptEvaluatorApi.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithDuckTypedInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.MonoEvaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void PerformanceTest(int count = -1) + { + var code = @"int Sqr(int a) + { + return a * a; + }"; + + if (count != -1) + code += "//" + count; //this unique extra code comment ensures the code to be compiled cannot be cached + + dynamic script = CSScript.Evaluator + .CompileMethod(code) + .CreateObject("*"); + + var r = script.Sqr(3); + } + + public void DebugTest() + { + //pops up an assertion dialog + + CSScript.EvaluatorConfig.DebugBuild = true; + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }"); + + var r = script.Sum(3, 4); + } + } + + public static void Profile() + { + var sw = new Stopwatch(); + var samples = new EvaluatorSamples(); + var count = 20; + var inxed = 0; + bool preventCaching = false; + + Action run = () => + { + sw.Restart(); + for (int i = 0; i < count; i++) + if (preventCaching) + samples.PerformanceTest(inxed++); + else + samples.PerformanceTest(); + + Console.WriteLine(CSScript.Evaluator.GetType().Name + ": " + sw.ElapsedMilliseconds); + }; + + Action runAll = () => + { + Console.WriteLine("\n---------------------------------------------"); + Console.WriteLine($"Caching enabled: {!preventCaching}\n"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + run(); + }; + + RoslynEvaluator.LoadCompilers(); //Roslyn is extremely heavy so exclude startup time from profiling + + Console.WriteLine("Testing performance"); + + preventCaching = true; + runAll(); + + preventCaching = false; + runAll(); + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public class InputData + { + public int Index = 0; + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/Scripting.native.cs b/FRBDK/GlueView2Test/GlueView2/Scripting.native.cs new file mode 100644 index 000000000..37c7ef2f7 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Scripting.native.cs @@ -0,0 +1,354 @@ +using CSScriptLibrary; +using System; +using System.Linq; +using System.IO; +using csscript; +using System.CodeDom.Compiler; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Native interface (API). +// This API is a compiler specific interface, which relies solely on CodeDom compiler. In most of the cases +// CS-Script Native model is the most flexible and natural choice +// +// Apart from Native API CS-Script offers alternative hosting model: CS-Script Evaluator, which provides +// a unified generic interface allowing dynamic switch the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// The Native interface is the original API that was designed to take maximum advantage of the dynamic C# code +// execution with CodeDom. The original implementation of this API was developed even before any compiler-as-service +// solution became available. Being based solely on CodeDOM the API doesn't utilize neither Mono nor Roslyn +// scripting solutions. Despite that CS-Script Native is the most mature, powerful and flexible API available with CS-Script. +// +// Native interface allows some unique features that are not available with CS-Script Evaluator: +// - Debugging scripts +// - Script caching +// - Script unloading + +namespace CSScriptNativeApi +{ + public class HostApp + { + public static void Test() + { + var host = new HostApp(); + host.Log("Testing compiling services CS-Script Native API"); + Console.WriteLine("---------------------------------------------"); + + CodeDomSamples.LoadMethod_Instance(); + CodeDomSamples.LoadMethod_Static(); + CodeDomSamples.LoadDelegate(); + CodeDomSamples.CreateAction(); + CodeDomSamples.CreateFunc(); + CodeDomSamples.LoadCode(); + CodeDomSamples.LoadCode_WithInterface(host); + CodeDomSamples.LoadCode_WithDuckTypedInterface(host); + CodeDomSamples.ExecuteAndUnload(); + //CodeDomSamples.DebugTest(); //uncomment if want to fire an assertion during the script execution + } + + public class CodeDomSamples + { + public static void LoadMethod_Instance() + { + // 1- LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.LoadMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public static void LoadMethod_Static() + { + // 1 - LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - GetStaticMethod returns first found static method as a duck-typed delegate that + // accepts 'params object[]' arguments + // + // Note: you can use GetStaticMethodWithArgs for higher precision method search: GetStaticMethodWithArgs("*.SayHello", typeof(string)); + var sayHello = CSScript.LoadMethod(@"static void SayHello(string greeting) + { + Console.WriteLine(greeting); + }") + .GetStaticMethod(); + + sayHello("Hello World!"); + } + + public static void LoadDelegate() + { + // LoadDelegate wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + // The 'using System;' is optional; it demonstrates how to specify 'using' in the method-only syntax + + var sayHello = CSScript.LoadDelegate>( + @"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateAction() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sayHello = CSScript.CreateAction(@"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateFunc() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // int as a return type. + + var Sqr = CSScript.CreateFunc(@"int Sqr(int a) + { + return a * a; + }"); + int r = Sqr(3); + } + + public static void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + + public static void LoadCodeWithConfig() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + string file = Path.GetTempFileName(); + try + { + File.WriteAllText(file, @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + var settings = new Settings(); + //settings = null; // set to null to foll back to defaults + + dynamic script = CSScript.LoadWithConfig(file, null, false, settings, "/define:TEST") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + finally + { + if (File.Exists(file)) + File.Delete(file); + } + } + + public static void LoadCode_WithInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly. + // 2 - The script class implements host app interface so the returned object can be type casted into it. + // 3 - In this sample host object is passed into script routine. + + var calc = (ICalc) CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script : ICalc + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*"); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void LoadCode_WithDuckTypedInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + // 3 - In this sample host object is passed into script routine. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*") + .AlignToInterface(); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void ExecuteAndUnload() + { + // The script will be loaded into a temporary AppDomain and unloaded after the execution. + + // Note: remote execution is a subject of some restrictions associated with the nature of the + // CLR cross-AppDomain interaction model: + // * the script class must be serializable or derived from MarshalByRefObject. + // + // * any object (call arguments, return objects) that crosses ApPDomain boundaries + // must be serializable or derived from MarshalByRefObject. + // + // * long living script class instances may get disposed in remote domain even if they are + // being referenced in the current AppDomain. You need to use the usual .NET techniques + // to prevent that. See LifetimeManagement.cs sample for details. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var code = @"using System; + public class Script : MarshalByRefObject + { + public void Hello(string greeting) + { + Console.WriteLine(greeting); + } + }"; + + //Note: usage of helper.CreateAndAlignToInterface("Script") is also acceptable + using (var helper = new AsmHelper(CSScript.CompileCode(code), null, deleteOnExit: true)) + { + IScript script = helper.CreateAndAlignToInterface("*"); + script.Hello("Hi there..."); + } + //from this point AsmHelper is disposed and the temp AppDomain is unloaded + } + + public static void DebugTest() + { + //pops up an assertion dialog + dynamic script = CSScript.LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }", null, debugBuild: true).CreateObject("*"); + + int result = script.Sum(1, 2); + } + } + + public void Log(string message) + { + Console.WriteLine(message); + } + } + + public interface IScript + { + void Hello(string greeting); + } + + public interface ICalc + { + HostApp Host { get; set; } + + int Sum(int a, int b); + } + + public class Samples + { + static public void CompilingHistory() + { + string script = Path.GetTempFileName(); + string scriptAsm = script + ".dll"; + CSScript.KeepCompilingHistory = true; + + try + { + File.WriteAllText(script, @"using System; + using System.Windows.Forms; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + + + CSScript.CompileFile(script, scriptAsm, false, null); + + CompilingInfo info = CSScript.CompilingHistory + .Values + .FirstOrDefault(item => item.ScriptFile == script); + if (info != null) + { + Console.WriteLine("Script: " + info.ScriptFile); + + Console.WriteLine("Referenced assemblies:"); + foreach (string asm in info.Input.ReferencedAssemblies) + Console.WriteLine(asm); + + if (info.Result.Errors.HasErrors) + { + foreach (CompilerError err in info.Result.Errors) + if (!err.IsWarning) + Console.WriteLine("Error: " + err.ErrorText); + } + } + + CSScript.CompilingHistory.Clear(); + + } + finally + { + CSScript.KeepCompilingHistory = false; + } + } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/Setup/CameraSetup.cs b/FRBDK/GlueView2Test/GlueView2/Setup/CameraSetup.cs new file mode 100644 index 000000000..aa78d9347 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/Setup/CameraSetup.cs @@ -0,0 +1,148 @@ + // This is a generated file created by Glue. To change this file, edit the camera settings in Glue. + // To access the camera settings, push the camera icon. + using Camera = FlatRedBall.Camera; + namespace GlueView2 + { + public class CameraSetupData + { + public float Scale { get; set; } + public bool Is2D { get; set; } + public int ResolutionWidth { get; set; } + public int ResolutionHeight { get; set; } + public decimal? AspectRatio { get; set; } + public bool AllowWidowResizing { get; set; } + public bool IsFullScreen { get; set; } + public ResizeBehavior ResizeBehavior { get; set; } + public WidthOrHeight DominantInternalCoordinates { get; set; } + } + public enum ResizeBehavior + { + StretchVisibleArea, + IncreaseVisibleArea + } + public enum WidthOrHeight + { + Width, + Height + } + internal static class CameraSetup + { + static Microsoft.Xna.Framework.GraphicsDeviceManager graphicsDeviceManager; + public static CameraSetupData Data = new CameraSetupData + { + Scale = 100f, + ResolutionWidth = 800, + ResolutionHeight = 600, + Is2D = true, + IsFullScreen = false, + AllowWidowResizing = false, + ResizeBehavior = ResizeBehavior.StretchVisibleArea, + DominantInternalCoordinates = WidthOrHeight.Height, + } + ; + internal static void ResetCamera (Camera cameraToReset = null) + { + if (cameraToReset == null) + { + cameraToReset = FlatRedBall.Camera.Main; + } + cameraToReset.Orthogonal = Data.Is2D; + if (Data.Is2D) + { + cameraToReset.OrthogonalHeight = Data.ResolutionHeight; + cameraToReset.OrthogonalWidth = Data.ResolutionWidth; + cameraToReset.FixAspectRatioYConstant(); + } + if (Data.AspectRatio != null) + { + SetAspectRatioTo(Data.AspectRatio.Value, Data.DominantInternalCoordinates, Data.ResolutionWidth, Data.ResolutionHeight); + } + } + internal static void SetupCamera (Camera cameraToSetUp, Microsoft.Xna.Framework.GraphicsDeviceManager graphicsDeviceManager) + { + CameraSetup.graphicsDeviceManager = graphicsDeviceManager; + ResetWindow(); + ResetCamera(cameraToSetUp); + FlatRedBall.FlatRedBallServices.GraphicsOptions.SizeOrOrientationChanged += HandleResolutionChange; + } + internal static void ResetWindow () + { + #if WINDOWS || DESKTOP_GL + FlatRedBall.FlatRedBallServices.Game.Window.AllowUserResizing = Data.AllowWidowResizing; + if (Data.IsFullScreen) + { + #if DESKTOP_GL + graphicsDeviceManager.HardwareModeSwitch = false; + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution(Microsoft.Xna.Framework.Graphics.GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, Microsoft.Xna.Framework.Graphics.GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height, FlatRedBall.Graphics.WindowedFullscreenMode.FullscreenBorderless); + #elif WINDOWS + System.IntPtr hWnd = FlatRedBall.FlatRedBallServices.Game.Window.Handle; + var control = System.Windows.Forms.Control.FromHandle(hWnd); + var form = control.FindForm(); + form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + form.WindowState = System.Windows.Forms.FormWindowState.Maximized; + #endif + } + else + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + } + #elif IOS || ANDROID + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetFullScreen(FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth, FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight); + #elif UWP + if (Data.IsFullScreen) + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetFullScreen(Data.ResolutionWidth, Data.ResolutionHeight); + } + else + { + FlatRedBall.FlatRedBallServices.GraphicsOptions.SetResolution((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + var newWindowSize = new Windows.Foundation.Size((int)(Data.ResolutionWidth * Data.Scale/ 100.0f), (int)(Data.ResolutionHeight * Data.Scale/ 100.0f)); + Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TryResizeView(newWindowSize); + } + #endif + } + private static void HandleResolutionChange (object sender, System.EventArgs args) + { + if (Data.AspectRatio != null) + { + SetAspectRatioTo(Data.AspectRatio.Value, Data.DominantInternalCoordinates, Data.ResolutionWidth, Data.ResolutionHeight); + } + if (Data.Is2D && Data.ResizeBehavior == ResizeBehavior.IncreaseVisibleArea) + { + FlatRedBall.Camera.Main.OrthogonalHeight = FlatRedBall.Camera.Main.DestinationRectangle.Height / (Data.Scale/ 100.0f); + FlatRedBall.Camera.Main.FixAspectRatioYConstant(); + } + } + private static void SetAspectRatioTo (decimal aspectRatio, WidthOrHeight dominantInternalCoordinates, int desiredWidth, int desiredHeight) + { + var resolutionAspectRatio = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth / (decimal)FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight; + int destinationRectangleWidth; + int destinationRectangleHeight; + int x = 0; + int y = 0; + if (aspectRatio > resolutionAspectRatio) + { + destinationRectangleWidth = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth; + destinationRectangleHeight = FlatRedBall.Math.MathFunctions.RoundToInt(destinationRectangleWidth / (float)aspectRatio); + y = (FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight - destinationRectangleHeight) / 2; + } + else + { + destinationRectangleHeight = FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionHeight; + destinationRectangleWidth = FlatRedBall.Math.MathFunctions.RoundToInt(destinationRectangleHeight * (float)aspectRatio); + x = (FlatRedBall.FlatRedBallServices.GraphicsOptions.ResolutionWidth - destinationRectangleWidth) / 2; + } + FlatRedBall.Camera.Main.DestinationRectangle = new Microsoft.Xna.Framework.Rectangle(x, y, destinationRectangleWidth, destinationRectangleHeight); + if (dominantInternalCoordinates == WidthOrHeight.Height) + { + FlatRedBall.Camera.Main.OrthogonalHeight = desiredHeight; + FlatRedBall.Camera.Main.FixAspectRatioYConstant(); + } + else + { + FlatRedBall.Camera.Main.OrthogonalWidth = desiredWidth; + FlatRedBall.Camera.Main.FixAspectRatioXConstant(); + } + } + } + } diff --git a/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs new file mode 100644 index 000000000..9ae900d11 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableListVsTileShapeCollectionRelationship.cs @@ -0,0 +1,87 @@ +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + public class CollidableListVsTileShapeCollectionRelationship : + CollisionRelationship + where FirstCollidableT : PositionedObject, ICollidable + { + CollidableVsTileShapeCollectionData data; + + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCircle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionRectangle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionPolygon = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCollidable = subCollisionFunc; } + + public Action CollisionOccurred; + + PositionedObjectList list; + + public override object FirstAsObject => list; + public override object SecondAsObject => data.TileShapeCollection; + + public CollidableListVsTileShapeCollectionRelationship(PositionedObjectList list, TileShapeCollection tileShapeCollection) + { + data = new CollidableVsTileShapeCollectionData(tileShapeCollection); + this.list = list; + } + + public override bool DoCollisions() + { + bool didCollisionOccur = false; + if (skippedFrames < FrameSkip) + { + skippedFrames++; + } + else + { + if (CollisionLimit == CollisionLimit.Closest || CollisionLimit == CollisionLimit.First) + { + string message = $"{nameof(CollidableVsTileShapeCollectionRelationship)} does not implement CollisionLimit {CollisionLimit}"; + throw new NotImplementedException(); + } + else + { + skippedFrames = 0; + + for(int i = list.Count - 1 ; i > -1; i--) + { + var singleObject = list[i]; + + var didCollide = false; + // todo - tile shape collections need to report their deep collision, they don't currently: + if (CollisionType == CollisionType.EventOnlyCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionEventOnly(singleObject); + } + else if (CollisionType == CollisionType.MoveCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionMove(singleObject); + } + else if (CollisionType == CollisionType.BounceCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionBounce(singleObject, bounceElasticity); + } + else + { + throw new NotImplementedException(); + } + + if (didCollide) + { + didCollisionOccur = true; + CollisionOccurred?.Invoke(singleObject, data.TileShapeCollection); + } + } + } + } + return didCollisionOccur; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs new file mode 100644 index 000000000..20a06ca64 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollidableVsTileShapeCollectionRelationship.cs @@ -0,0 +1,189 @@ +using FlatRedBall; +using FlatRedBall.Math.Collision; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + class CollidableVsTileShapeCollectionData + where FirstCollidableT : PositionedObject, ICollidable + { + TileShapeCollection tileShapeCollection; + public TileShapeCollection TileShapeCollection { get { return tileShapeCollection; } } + + public Func firstSubCollisionCircle; + public Func firstSubCollisionRectangle; + public Func firstSubCollisionPolygon; + public Func firstSubCollisionCollidable; + + public CollidableVsTileShapeCollectionData(TileShapeCollection tileShapeCollection) + { + this.tileShapeCollection = tileShapeCollection; + } + + public bool CollideAgainstConsiderSubCollisionEventOnly(FirstCollidableT singleObject) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainst(circle); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainst(rectangle); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainst(polygon); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainst(collidable); + } + else + { + return this.tileShapeCollection.CollideAgainst(singleObject); + } + } + + public bool CollideAgainstConsiderSubCollisionMove(FirstCollidableT singleObject) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(circle); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(rectangle); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(polygon); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainstSolid(collidable); + } + else + { + return this.tileShapeCollection.CollideAgainstSolid(singleObject); + } + } + + public bool CollideAgainstConsiderSubCollisionBounce(FirstCollidableT singleObject, float bounceElasticity) + { + if (firstSubCollisionCircle != null) + { + var circle = firstSubCollisionCircle(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(circle, bounceElasticity); + } + else if (firstSubCollisionRectangle != null) + { + var rectangle = firstSubCollisionRectangle(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(rectangle, bounceElasticity); + } + else if (firstSubCollisionPolygon != null) + { + var polygon = firstSubCollisionPolygon(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(polygon, bounceElasticity); + } + else if (firstSubCollisionCollidable != null) + { + var collidable = firstSubCollisionCollidable(singleObject); + return this.tileShapeCollection.CollideAgainstBounce(collidable, bounceElasticity); + } + else + { + return this.tileShapeCollection.CollideAgainstBounce(singleObject, bounceElasticity); + } + } + } + + + public class CollidableVsTileShapeCollectionRelationship : CollisionRelationship + where FirstCollidableT : PositionedObject, ICollidable + { + CollidableVsTileShapeCollectionData data; + + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCircle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionRectangle = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionPolygon = subCollisionFunc; } + public void SetFirstSubCollision(Func subCollisionFunc) { data.firstSubCollisionCollidable = subCollisionFunc; } + + public Action CollisionOccurred; + + + FirstCollidableT singleObject; + + public override object FirstAsObject => singleObject; + public override object SecondAsObject => data.TileShapeCollection; + + public CollidableVsTileShapeCollectionRelationship(FirstCollidableT singleObject, TileShapeCollection tileShapeCollection) + { + data = new CollidableVsTileShapeCollectionData(tileShapeCollection); + this.singleObject = singleObject; + } + + public override bool DoCollisions() + { + bool didCollisionOccur = false; + + if (skippedFrames < FrameSkip) + { + skippedFrames++; + } + else + { + if (CollisionLimit == CollisionLimit.Closest || CollisionLimit == CollisionLimit.First) + { + string message = $"{nameof(CollidableVsTileShapeCollectionRelationship)} does not implement CollisionLimit {CollisionLimit}"; + throw new NotImplementedException(); + } + else + { + skippedFrames = 0; + + var didCollide = false; + // todo - tile shape collections need to report their deep collision, they don't currently: + if (CollisionType == CollisionType.EventOnlyCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionEventOnly(singleObject); + } + else if (CollisionType == CollisionType.MoveCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionMove(singleObject); + } + else if (CollisionType == CollisionType.BounceCollision) + { + didCollide = data.CollideAgainstConsiderSubCollisionBounce(singleObject, bounceElasticity); + } + else + { + throw new NotImplementedException(); + } + + if(didCollide) + { + CollisionOccurred?.Invoke(singleObject, data.TileShapeCollection); + + didCollisionOccur = true; + } + } + } + + return didCollisionOccur; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs new file mode 100644 index 000000000..56be148cb --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileCollisions/CollisionManagerTileShapeCollectionExtensions.cs @@ -0,0 +1,41 @@ +using FlatRedBall.Math.Collision; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileCollisions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlatRedBall.Math.Collision +{ + public static class CollisionManagerTileShapeCollectionExtensions + { + public static CollidableVsTileShapeCollectionRelationship CreateTileRelationship( + this CollisionManager collisionManager, + FirstCollidableT collidable, TileShapeCollection tileShapeCollection) + where FirstCollidableT : PositionedObject, ICollidable + { + var relationship = new CollidableVsTileShapeCollectionRelationship( + collidable, tileShapeCollection); + + CollisionManager.Self.Relationships.Add(relationship); + + return relationship; + } + + public static CollidableListVsTileShapeCollectionRelationship CreateTileRelationship( + this CollisionManager collisionManager, + PositionedObjectList collidable, TileShapeCollection tileShapeCollection) + where FirstCollidableT : PositionedObject, ICollidable + { + var relationship = new CollidableListVsTileShapeCollectionRelationship( + collidable, tileShapeCollection); + + CollisionManager.Self.Relationships.Add(relationship); + + return relationship; + } + + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileCollisions/TileShapeCollection.cs b/FRBDK/GlueView2Test/GlueView2/TileCollisions/TileShapeCollection.cs new file mode 100644 index 000000000..81acffd97 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileCollisions/TileShapeCollection.cs @@ -0,0 +1,930 @@ +using FlatRedBall.Math; +using FlatRedBall.Math.Geometry; +using FlatRedBall.TileGraphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FlatRedBall.TileCollisions +{ + public partial class TileShapeCollection + { + #region Fields + + ShapeCollection mShapes; + Axis mSortAxis = Axis.X; + float mLeftSeedX = 0; + float mBottomSeedY = 0; + float mGridSize; + bool mVisible = true; + + + bool mFirstTimeSortAxisSet = true; + + #endregion + + #region Properties + + public Axis SortAxis + { + get + { + return mSortAxis; + } + set + { + bool hasChanged = value != mSortAxis; + if (hasChanged || mFirstTimeSortAxisSet) + { + mSortAxis = value; + PerformSort(); + } + } + } + + public float GridSize + { + get { return mGridSize; } + set + { +#if DEBUG + if (value < 0) + { + throw new Exception("GridSize needs to be positive"); + } +#endif + + + mGridSize = value; + mShapes.MaxAxisAlignedRectanglesScale = mGridSize; + } + } + + public PositionedObjectList Rectangles + { + get { return mShapes.AxisAlignedRectangles; } + } + + + public PositionedObjectList Polygons + { + get { return mShapes.Polygons; } + } + + public string Name { get; set; } + + + public List LastCollisionPolygons => mShapes.LastCollisionPolygons; + public List LastCollisionAxisAlignedRectangles => mShapes.LastCollisionAxisAlignedRectangles; + + public bool Visible + { + get { return mVisible; } + set + { + mVisible = value; + for (int i = 0; i < mShapes.AxisAlignedRectangles.Count; i++) + { + mShapes.AxisAlignedRectangles[i].Visible = value; + } + for (int i = 0; i < mShapes.Polygons.Count; i++) + { + if (value) + { + // to get the verts to show up + mShapes.Polygons[i].ForceUpdateDependencies(); + } + mShapes.Polygons[i].Visible = value; + } + } + } + + #endregion + + public TileShapeCollection() + { + mShapes = new ShapeCollection(); + GridSize = 16; + } + + + public void AddToLayer(FlatRedBall.Graphics.Layer layer) + { + this.mShapes.AddToManagers(layer); + } + + public bool CollideAgainstSolid(AxisAlignedRectangle movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Circle movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Polygon movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstSolid(Line movableObject) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(movableObject, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainst(AxisAlignedRectangle rectangle) + { + return mShapes.CollideAgainst(rectangle, true, mSortAxis); + } + + public bool CollideAgainst(Circle circle) + { + return mShapes.CollideAgainst(circle, true, mSortAxis); + } + + public bool CollideAgainst(Polygon polygon) + { + return mShapes.CollideAgainst(polygon, true, mSortAxis); + } + + public bool CollideAgainst(Line line) + { + return mShapes.CollideAgainst(line, true, mSortAxis); + } + + public bool CollideAgainst(ICollidable collidable) + { + return mShapes.CollideAgainst(collidable.Collision, true, mSortAxis); + } + + public bool CollideAgainstSolid(ICollidable collidable) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(collidable.Collision, true, mSortAxis, 1, 0, 0); + + return toReturn; + } + + public bool CollideAgainstBounce(ICollidable collidable, float elasticity) + { + bool toReturn = false; + + toReturn = mShapes.CollideAgainstBounce(collidable.Collision, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(AxisAlignedRectangle rectangle, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(rectangle, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(Circle circle, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(circle, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + public bool CollideAgainstBounce(Polygon polygon, float elasticity) + { + bool toReturn = mShapes.CollideAgainstBounce(polygon, true, mSortAxis, 1, 0, elasticity); + + return toReturn; + } + + [Obsolete("Use GetRectangleAtPosition instead as it more clearly indicates what the method does.")] + public AxisAlignedRectangle GetTileAt(float x, float y) + { + return GetRectangleAtPosition(x, y); + } + + public AxisAlignedRectangle GetRectangleAtPosition(float worldX, float worldY) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + float keyValue = GetCoordinateValueForPartitioning(middleOfTileX, middleOfTileY); + + float keyValueBefore = keyValue - GridSize / 2.0f; + float keyValueAfter = keyValue + GridSize / 2.0f; + + int startInclusive = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValueBefore, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + + int endExclusive = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValueAfter, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + AxisAlignedRectangle toReturn = GetRectangleAtPosition(worldX, worldY, startInclusive, endExclusive); + + return toReturn; + } + + public Polygon GetPolygonAtPosition(float worldX, float worldY) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + float keyValue = GetCoordinateValueForPartitioning(middleOfTileX, middleOfTileY); + + var halfGridSize = GridSize / 2.0f; + + float keyValueBefore = keyValue - halfGridSize; + float keyValueAfter = keyValue + halfGridSize; + + int startInclusive = mShapes.Polygons.GetFirstAfter(keyValueBefore, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + + int endExclusive = mShapes.Polygons.GetFirstAfter(keyValueAfter, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + var left = middleOfTileX - halfGridSize; + var right = middleOfTileX + halfGridSize; + var top = middleOfTileY + halfGridSize; + var bottom = middleOfTileY - halfGridSize; + + for (int i = startInclusive; i < endExclusive; i++) + { + var polygon = mShapes.Polygons[i]; + + if (polygon.Position.X > left && polygon.Position.X < right && + polygon.Position.Y > bottom && polygon.Position.Y < top) + { + return polygon; + } + } + + return null; + } + + private Polygon GetPolygonAtPosition(float worldX, float worldY, int startInclusive, int endExclusive) + { + float middleOfTileX = MathFunctions.RoundFloat(worldX, GridSize, mLeftSeedX + GridSize / 2.0f); + float middleOfTileY = MathFunctions.RoundFloat(worldY, GridSize, mBottomSeedY + GridSize / 2.0f); + + var halfGridSize = GridSize / 2.0f; + + var left = middleOfTileX - halfGridSize; + var right = middleOfTileX + halfGridSize; + var top = middleOfTileY + halfGridSize; + var bottom = middleOfTileY - halfGridSize; + + for (int i = startInclusive; i < endExclusive; i++) + { + var polygon = mShapes.Polygons[i]; + + if (polygon.Position.X > left && polygon.Position.X < right && + polygon.Position.Y > bottom && polygon.Position.Y < top) + { + return polygon; + } + } + + return null; + } + + private AxisAlignedRectangle GetRectangleAtPosition(float x, float y, int startInclusive, int endExclusive) + { + AxisAlignedRectangle toReturn = null; + for (int i = startInclusive; i < endExclusive; i++) + { + if (mShapes.AxisAlignedRectangles[i].IsPointInside(x, y)) + { + toReturn = mShapes.AxisAlignedRectangles[i]; + break; + } + } + return toReturn; + } + + public void AddCollisionAtWorld(float x, float y) + { + // Make sure there isn't already collision here + if (GetRectangleAtPosition(x, y) == null) + { + // x and y + // represent + // the center + // of the tile + // where the user + // may want to add + // collision. Let's + // subtract half width/ + // height so we can use the + // bottom/left + float roundedX = MathFunctions.RoundFloat(x - GridSize / 2.0f, GridSize, mLeftSeedX); + float roundedY = MathFunctions.RoundFloat(y - GridSize / 2.0f, GridSize, mBottomSeedY); + + AxisAlignedRectangle newAar = new AxisAlignedRectangle(); + newAar.Width = GridSize; + newAar.Height = GridSize; + newAar.Left = roundedX; + newAar.Bottom = roundedY; + + if (this.mVisible) + { + newAar.Visible = true; + } + + float keyValue = GetCoordinateValueForPartitioning(roundedX, roundedY); + + int index = mShapes.AxisAlignedRectangles.GetFirstAfter(keyValue, mSortAxis, + 0, mShapes.AxisAlignedRectangles.Count); + + mShapes.AxisAlignedRectangles.Insert(index, newAar); + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(newAar); + + newAar.RepositionDirections = directions; + } + } + + public void RemoveCollisionAtWorld(float x, float y) + { + AxisAlignedRectangle existing = GetTileAt(x, y); + if (existing != null) + { + ShapeManager.Remove(existing); + + float keyValue = GetCoordinateValueForPartitioning(existing.X, existing.Y); + + float keyValueBefore = keyValue - GridSize * 3 / 2.0f; + float keyValueAfter = keyValue + GridSize * 3 / 2.0f; + + int before = Rectangles.GetFirstAfter(keyValueBefore, mSortAxis, 0, Rectangles.Count); + int after = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Rectangles.Count); + + AxisAlignedRectangle leftOf = GetRectangleAtPosition(existing.X - GridSize, existing.Y, before, after); + AxisAlignedRectangle rightOf = GetRectangleAtPosition(existing.X + GridSize, existing.Y, before, after); + AxisAlignedRectangle above = GetRectangleAtPosition(existing.X, existing.Y + GridSize, before, after); + AxisAlignedRectangle below = GetRectangleAtPosition(existing.X, existing.Y - GridSize, before, after); + + if (leftOf != null && (leftOf.RepositionDirections & RepositionDirections.Right) != RepositionDirections.Right) + { + leftOf.RepositionDirections |= RepositionDirections.Right; + + } + if (rightOf != null && (rightOf.RepositionDirections & RepositionDirections.Left) != RepositionDirections.Left) + { + rightOf.RepositionDirections |= RepositionDirections.Left; + } + + if (above != null && (above.RepositionDirections & RepositionDirections.Down) != RepositionDirections.Down) + { + above.RepositionDirections |= RepositionDirections.Down; + } + + if (below != null && (below.RepositionDirections & RepositionDirections.Up) != RepositionDirections.Up) + { + below.RepositionDirections |= RepositionDirections.Up; + } + + + } + + + } + + public void RemoveSurroundedCollision() + { + for (int i = Rectangles.Count - 1; i > -1; i--) + { + var rectangle = Rectangles[i]; + if (rectangle.RepositionDirections == RepositionDirections.None) + { + rectangle.Visible = false; + this.Rectangles.Remove(rectangle); + } + } + } + + + private float GetCoordinateValueForPartitioning(float x, float y) + { + float keyValue = 0; + + switch (mSortAxis) + { + case Axis.X: + keyValue = x; + break; + case Axis.Y: + keyValue = y; + break; + case Axis.Z: + throw new NotImplementedException("Sorting on Z not supported"); + } + return keyValue; + } + + private RepositionDirections UpdateRepositionForNeighborsAndGetThisRepositionDirection(PositionedObject positionedObject) + { + // Let's see what is surrounding this rectangle and update it and the surrounding rects appropriately + float keyValue = GetCoordinateValueForPartitioning(positionedObject.Position.X, positionedObject.Position.Y); + + float keyValueBefore = keyValue - GridSize * 3 / 2.0f; + float keyValueAfter = keyValue + GridSize * 3 / 2.0f; + + int rectanglesBeforeIndex = Rectangles.GetFirstAfter(keyValueBefore, mSortAxis, 0, Rectangles.Count); + int rectanglesAfterIndex = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Rectangles.Count); + + int polygonsBeforeIndex = Polygons.GetFirstAfter(keyValueBefore, mSortAxis, 0, Polygons.Count); + int polygonsAfterIndex = Rectangles.GetFirstAfter(keyValueAfter, mSortAxis, 0, Polygons.Count); + + + float leftOfX = positionedObject.Position.X - GridSize; + float rightOfX = positionedObject.Position.X + GridSize; + float middleX = positionedObject.Position.X; + + float aboveY = positionedObject.Position.Y + GridSize; + float belowY = positionedObject.Position.Y - GridSize; + float middleY = positionedObject.Position.Y; + + AxisAlignedRectangle rectangleLeftOf = GetRectangleAtPosition(leftOfX, middleY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleRightOf = GetRectangleAtPosition(rightOfX, middleY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleAbove = GetRectangleAtPosition(middleX, aboveY, rectanglesBeforeIndex, rectanglesAfterIndex); + AxisAlignedRectangle rectangleBelow = GetRectangleAtPosition(middleX, belowY, rectanglesBeforeIndex, rectanglesAfterIndex); + + RepositionDirections directions = RepositionDirections.All; + if (rectangleLeftOf != null) + { + directions -= RepositionDirections.Left; + if ((rectangleLeftOf.RepositionDirections & RepositionDirections.Right) == RepositionDirections.Right) + { + rectangleLeftOf.RepositionDirections -= RepositionDirections.Right; + } + } + else + { + var polygon = GetPolygonAtPosition(leftOfX, middleY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Left; + if ((polygon.RepositionDirections & RepositionDirections.Right) == RepositionDirections.Right) + { + polygon.RepositionDirections -= RepositionDirections.Right; + } + } + } + + if (rectangleRightOf != null) + { + directions -= RepositionDirections.Right; + + if ((rectangleRightOf.RepositionDirections & RepositionDirections.Left) == RepositionDirections.Left) + { + rectangleRightOf.RepositionDirections -= RepositionDirections.Left; + } + } + else + { + var polygon = GetPolygonAtPosition(rightOfX, middleY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Right; + if ((polygon.RepositionDirections & RepositionDirections.Left) == RepositionDirections.Left) + { + polygon.RepositionDirections -= RepositionDirections.Left; + } + } + } + + + + if (rectangleAbove != null) + { + directions -= RepositionDirections.Up; + + if ((rectangleAbove.RepositionDirections & RepositionDirections.Down) == RepositionDirections.Down) + { + rectangleAbove.RepositionDirections -= RepositionDirections.Down; + } + } + else + { + var polygon = GetPolygonAtPosition(middleX, aboveY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Up; + + if ((polygon.RepositionDirections & RepositionDirections.Down) == RepositionDirections.Down) + { + polygon.RepositionDirections -= RepositionDirections.Down; + } + } + } + + if (rectangleBelow != null) + { + directions -= RepositionDirections.Down; + if ((rectangleBelow.RepositionDirections & RepositionDirections.Up) == RepositionDirections.Up) + { + rectangleBelow.RepositionDirections -= RepositionDirections.Up; + } + } + else + { + var polygon = GetPolygonAtPosition(middleX, belowY, polygonsBeforeIndex, polygonsAfterIndex); + + if (polygon != null) + { + directions -= RepositionDirections.Down; + + if ((polygon.RepositionDirections & RepositionDirections.Up) == RepositionDirections.Up) + { + polygon.RepositionDirections -= RepositionDirections.Up; + } + } + } + + return directions; + } + + public void RemoveFromManagersOneWay() + { + this.mShapes.MakeOneWay(); + this.mShapes.RemoveFromManagers(); + this.mShapes.MakeTwoWay(); + } + + public void RemoveFromManagers() + { + this.mShapes.RemoveFromManagers(); + } + + private void PerformSort() + { + switch (mSortAxis) + { + case Axis.X: + mShapes.AxisAlignedRectangles.SortXInsertionAscending(); + mShapes.Polygons.SortXInsertionAscending(); + break; + case Axis.Y: + mShapes.AxisAlignedRectangles.SortYInsertionAscending(); + mShapes.Polygons.SortYInsertionAscending(); + break; + case Axis.Z: + mShapes.AxisAlignedRectangles.SortZInsertionAscending(); + mShapes.Polygons.SortZInsertionAscending(); + break; + } + } + + public void SetColor(Microsoft.Xna.Framework.Color color) + { + foreach (var rectangle in this.Rectangles) + { + rectangle.Color = color; + } + } + + public void RefreshAllRepositionDirections() + { + var count = this.mShapes.AxisAlignedRectangles.Count; + for (int i = 0; i < count; i++) + { + var rectangle = this.mShapes.AxisAlignedRectangles[i]; + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(rectangle); + + rectangle.RepositionDirections = directions; + } + + count = this.mShapes.Polygons.Count; + for (int i = 0; i < count; i++) + { + var polygon = this.mShapes.Polygons[i]; + + var directions = UpdateRepositionForNeighborsAndGetThisRepositionDirection(polygon); + + polygon.RepositionDirections = directions; + } + } + + public override string ToString() + { + return Name; + } + } + + + + public static class TileShapeCollectionLayeredTileMapExtensions + { + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap, string nameToUse) + { + AddCollisionFrom(tileShapeCollection, layeredTileMap, + new List { nameToUse }); + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap, IEnumerable namesToUse) + { + Func, bool> predicate = (list) => + { + var nameProperty = list.FirstOrDefault(item => item.Name.ToLower() == "name"); + + return namesToUse.Contains(nameProperty.Value); + }; + + AddCollisionFrom(tileShapeCollection, layeredTileMap, predicate); + + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + foreach (var layer in layeredTileMap.MapLayers) + { + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + tileShapeCollection.AddCollisionAtWorld(centerX, + centerY); + } + } + } + } + } + } + + public static void AddMergedCollisionFrom(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + Dictionary> rectangleIndexes = new Dictionary>(); + + foreach (var layer in layeredTileMap.MapLayers) + { + AddCollisionFromLayerInternal(tileShapeCollection, predicate, properties, dimension, dimensionHalf, rectangleIndexes, layer); + } + + ApplyMerging(tileShapeCollection, dimension, rectangleIndexes); + } + + public static void AddMergedCollisionFromLayer(this TileShapeCollection tileShapeCollection, MapDrawableBatch layer, LayeredTileMap layeredTileMap, + Func, bool> predicate) + { + var properties = layeredTileMap.TileProperties; + float dimension = layeredTileMap.WidthPerTile.Value; + float dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + + Dictionary> rectangleIndexes = new Dictionary>(); + + AddCollisionFromLayerInternal(tileShapeCollection, predicate, properties, dimension, dimensionHalf, rectangleIndexes, layer); + + ApplyMerging(tileShapeCollection, dimension, rectangleIndexes); + } + + public static void AddCollisionFromTilesWithProperty(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, string propertyName) + { + tileShapeCollection.AddCollisionFrom( + layeredTileMap, (list) => list.Any(item => item.Name == propertyName)); + + } + + public static void AddMergedCollisionFromTilesWithProperty(this TileShapeCollection tileShapeCollection, LayeredTileMap layeredTileMap, string propertyName) + { + tileShapeCollection.AddMergedCollisionFrom( + layeredTileMap, (list) => list.Any(item => item.Name == propertyName)); + + } + + private static void ApplyMerging(TileShapeCollection tileShapeCollection, float dimension, Dictionary> rectangleIndexes) + { + foreach (var kvp in rectangleIndexes.OrderBy(item => item.Key)) + { + var rectanglePositionList = kvp.Value.OrderBy(item => item).ToList(); + + var firstValue = rectanglePositionList[0]; + var currentValue = firstValue; + var expectedValue = firstValue + 1; + for (int i = 1; i < rectanglePositionList.Count; i++) + { + if (rectanglePositionList[i] != expectedValue) + { + CloseRectangle(tileShapeCollection, kvp.Key, dimension, firstValue, currentValue); + + firstValue = rectanglePositionList[i]; + currentValue = firstValue; + } + else + { + currentValue++; + } + + expectedValue = currentValue + 1; + } + + CloseRectangle(tileShapeCollection, kvp.Key, dimension, firstValue, currentValue); + } + } + + private static void AddCollisionFromLayerInternal(TileShapeCollection tileShapeCollection, Func, bool> predicate, Dictionary> properties, float dimension, float dimensionHalf, Dictionary> rectangleIndexes, MapDrawableBatch layer) + { + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + + int key; + int value; + + if (tileShapeCollection.SortAxis == Axis.X) + { + key = (int)(centerX / dimension); + value = (int)(centerY / dimension); + } + else if (tileShapeCollection.SortAxis == Axis.Y) + { + key = (int)(centerY / dimension); + value = (int)(centerX / dimension); + } + else + { + throw new NotImplementedException("Cannot add tile collision on z-sorted shape collections"); + } + + List listToAddTo = null; + if (rectangleIndexes.ContainsKey(key) == false) + { + listToAddTo = new List(); + rectangleIndexes.Add(key, listToAddTo); + } + else + { + listToAddTo = rectangleIndexes[key]; + } + listToAddTo.Add(value); + + } + } + } + } + } + + private static void CloseRectangle(TileShapeCollection tileShapeCollection, int keyIndex, float dimension, int firstValue, int currentValue) + { + float x = 0; + float y = 0; + float width = dimension; + float height = dimension; + + if (tileShapeCollection.SortAxis == Axis.X) + { + x = (keyIndex + .5f) * dimension; + } + else + { + // y moves down so we subtract + y = (keyIndex - .5f) * dimension; + } + + var centerIndex = (firstValue + currentValue) / 2.0f; + + if (tileShapeCollection.SortAxis == Axis.X) + { + y = (centerIndex - .5f) * dimension; + height = (currentValue - firstValue + 1) * dimension; + } + else + { + x = (centerIndex + .5f) * dimension; + width = (currentValue - firstValue + 1) * dimension; + } + + AddRectangleStrip(tileShapeCollection, x, y, width, height); + } + + private static void AddRectangleStrip(TileShapeCollection tileShapeCollection, float x, float y, float width, float height) + { + AxisAlignedRectangle rectangle = new AxisAlignedRectangle(); + rectangle.X = x; + rectangle.Y = y; + rectangle.Width = width; + rectangle.Height = height; + + if (tileShapeCollection.Visible) + { + rectangle.Visible = true; + } + + tileShapeCollection.Rectangles.Add(rectangle); + } + + static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + Scene scene, IEnumerable namesToUse) + { + // prob need to clear out the tileShapeCollection + + float dimension = float.NaN; + float dimensionHalf = 0; + + for (int i = 0; i < scene.Sprites.Count; i++) + { + if (namesToUse.Contains(scene.Sprites[i].Name)) + { + + if (float.IsNaN(dimension)) + { + dimension = scene.Sprites[i].Width; + dimensionHalf = dimension / 2.0f; + tileShapeCollection.GridSize = dimension; + } + + tileShapeCollection.AddCollisionAtWorld(scene.Sprites[i].X, scene.Sprites[i].Y); + + } + + } + } + + public static void AddCollisionFrom(this TileShapeCollection tileShapeCollection, + LayeredTileMap layeredTileMap) + { + + var tilesWithCollision = layeredTileMap.TileProperties + .Where(item => item.Value.Any(property => property.Name == "HasCollision" && (string)property.Value == "True")) + .Select(item => item.Key).ToList(); + + tileShapeCollection.AddCollisionFrom(layeredTileMap, tilesWithCollision); + + } + + } + + +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileEntities/TileEntityInstantiator.cs b/FRBDK/GlueView2Test/GlueView2/TileEntities/TileEntityInstantiator.cs new file mode 100644 index 000000000..e05b85b75 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileEntities/TileEntityInstantiator.cs @@ -0,0 +1,525 @@ +using GlueView2.DataTypes; +using FlatRedBall.TileGraphics; +using System; +using System.Collections.Generic; +using System.Linq; +using GlueView2.Performance; +using FlatRedBall.Graphics; +using System.Reflection; +using TMXGlueLib.DataTypes; +using System.Collections; +using FlatRedBall.Math.Geometry; + +namespace FlatRedBall.TileEntities +{ + public static class TileEntityInstantiator + { + /// + /// A dictionary that stores all available values for a given type. + /// + /// + /// The structure of this class is a dictionary, where each entry in the dictionary is a list of dictionaries. This mgiht + /// seem confusing so let's look at an example. + /// This was originally created to cache values from CSVs. Let's say there's a type called PlatformerValues. + /// The class PlatformerValues would be the key in the dictionary. + /// Of course, platformer values can be defined in multiple entities (if multiple entities are platformers). + /// Each CSV in each entity becomes 1 dictionary, but since there can be multiple CSVs, there is a list. Therefore, the struture + /// might look like this: + /// * PlatformerValues + /// * List from Entity 1 + /// * OnGround + /// * InAir + /// * List from Entity 2 + /// * OnGround + /// * In Air + /// // and so on... + /// + static Dictionary> allDictionaries = new Dictionary>(); + + /// + /// Creates entities from a single layer for any tile with the EntityToCreate property. + /// + /// The layer to create entities from. + /// The map which contains the mapLayer instance. + public static void CreateEntitiesFrom(MapDrawableBatch mapLayer, LayeredTileMap layeredTileMap) + { + var entitiesToRemove = new List(); + + CreateEntitiesFrom(entitiesToRemove, mapLayer, layeredTileMap.TileProperties); + + foreach (var entityToRemove in entitiesToRemove) + { + string remove = entityToRemove; + mapLayer.RemoveTiles(t => t.Any(item => (item.Name == "EntityToCreate" || item.Name == "Type") && item.Value as string == remove), layeredTileMap.TileProperties); + } + + } + + public static void CreateEntitiesFrom(LayeredTileMap layeredTileMap) + { + var entitiesToRemove = new List(); + + foreach (var layer in layeredTileMap.MapLayers) + { + CreateEntitiesFrom(entitiesToRemove, layer, layeredTileMap.TileProperties); + } + foreach (var entityToRemove in entitiesToRemove) + { + string remove = entityToRemove; + layeredTileMap.RemoveTiles(t => t.Any(item => (item.Name == "EntityToCreate" || item.Name == "Type") && item.Value as string == remove), layeredTileMap.TileProperties); + } + foreach (var shapeCollection in layeredTileMap.ShapeCollections) + { + CreateEntitiesFromCircles(layeredTileMap, shapeCollection); + + CreateEntitiesFromRectangles(layeredTileMap, shapeCollection); + + CreateEntitiesFromPolygons(layeredTileMap, shapeCollection); + } + } + + private static void CreateEntitiesFromCircles(LayeredTileMap layeredTileMap, ShapeCollection shapeCollection) + { + var circles = shapeCollection.Circles; + for (int i = circles.Count - 1; i > -1; i--) + { + var circle = circles[i]; + if (!string.IsNullOrEmpty(circle.Name) && layeredTileMap.ShapeProperties.ContainsKey(circle.Name)) + { + var properties = layeredTileMap.ShapeProperties[circle.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = circle.Name; + ApplyPropertiesTo(entity, properties, circle.Position); + shapeCollection.Circles.Remove(circle); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.Circles.Add(circle); + circle.AttachTo(entity, false); + } + } + } + } + } + + private static void CreateEntitiesFromRectangles(LayeredTileMap layeredTileMap, ShapeCollection shapeCollection) + { + var rectangles = shapeCollection.AxisAlignedRectangles; + for (int i = rectangles.Count - 1; i > -1; i--) + { + var rectangle = rectangles[i]; + if (!string.IsNullOrEmpty(rectangle.Name) && layeredTileMap.ShapeProperties.ContainsKey(rectangle.Name)) + { + var properties = layeredTileMap.ShapeProperties[rectangle.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = rectangle.Name; + ApplyPropertiesTo(entity, properties, rectangle.Position); + shapeCollection.AxisAlignedRectangles.Remove(rectangle); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.AxisAlignedRectangles.Add(rectangle); + rectangle.AttachTo(entity, false); + } + + } + } + } + } + + private static void CreateEntitiesFromPolygons(LayeredTileMap layeredTileMap, Math.Geometry.ShapeCollection shapeCollection) + { + var polygons = shapeCollection.Polygons; + for (int i = polygons.Count - 1; i > -1; i--) + { + var polygon = polygons[i]; + if (!string.IsNullOrEmpty(polygon.Name) && layeredTileMap.ShapeProperties.ContainsKey(polygon.Name)) + { + var properties = layeredTileMap.ShapeProperties[polygon.Name]; + var entityAddingProperty = properties.FirstOrDefault(item => item.Name == "EntityToCreate" || item.Name == "Type"); + + var entityType = entityAddingProperty.Value as string; + if (!string.IsNullOrEmpty(entityType)) + { + IEntityFactory factory = GetFactory(entityType); + + var entity = factory.CreateNew(null) as PositionedObject; + + entity.Name = polygon.Name; + ApplyPropertiesTo(entity, properties, polygon.Position); + shapeCollection.Polygons.Remove(polygon); + + if (entity is Math.Geometry.ICollidable) + { + var entityCollision = (entity as Math.Geometry.ICollidable).Collision; + entityCollision.Polygons.Add(polygon); + polygon.AttachTo(entity, false); + } + + } + } + } + } + + private static void CreateEntitiesFrom(List entitiesToRemove, MapDrawableBatch layer, Dictionary> propertiesDictionary) + { + var flatRedBallLayer = SpriteManager.Layers.FirstOrDefault(item => item.Batches.Contains(layer)); + + var dictionary = layer.NamedTileOrderedIndexes; + + // layer needs its position updated: + layer.ForceUpdateDependencies(); + + foreach (var propertyList in propertiesDictionary.Values) + { + var property = + propertyList.FirstOrDefault(item2 => item2.Name == "EntityToCreate" || item2.Name == "Type"); + + if (!string.IsNullOrEmpty(property.Name)) + { + var tileName = propertyList.FirstOrDefault(item => item.Name.ToLowerInvariant() == "name").Value as string; + + var entityType = property.Value as string; + + if (!string.IsNullOrEmpty(entityType) && dictionary.ContainsKey(tileName)) + { + IEntityFactory factory = GetFactory(entityType); + + if (factory == null) + { + string message = + $"The factory for entity {entityType} could not be found. To create instances of this entity, " + + "set its 'CreatedByOtherEntities' property to true in Glue."; + throw new Exception(message); + } + else + { + entitiesToRemove.Add(entityType); + var indexList = dictionary[tileName]; + + foreach (var tileIndex in indexList) + { + var entity = factory.CreateNew(flatRedBallLayer) as PositionedObject; + + ApplyPropertiesTo(entity, layer, tileIndex, propertyList); + } + + } + } + } + } + } + + private static void ApplyPropertiesTo(PositionedObject entity, MapDrawableBatch layer, int tileIndex, List propertiesToAssign) + { + int vertexIndex = tileIndex * 4; + var dimension = + (layer.Vertices[vertexIndex + 1].Position - layer.Vertices[vertexIndex].Position).Length(); + + float dimensionHalf = dimension / 2.0f; + + + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(tileIndex, out left, out bottom); + Microsoft.Xna.Framework.Vector3 position = new Microsoft.Xna.Framework.Vector3(left, bottom, 0); + + var bottomRight = layer.Vertices[tileIndex * 4 + 1].Position; + + float xDifference = bottomRight.X - left; + float yDifference = bottomRight.Y - bottom; + + if (yDifference != 0 || xDifference < 0) + { + float angle = (float)System.Math.Atan2(yDifference, xDifference); + + entity.RotationZ = angle; + + } + + position += entity.RotationMatrix.Right * dimensionHalf; + position += entity.RotationMatrix.Up * dimensionHalf; + + position += layer.Position; + + ApplyPropertiesTo(entity, propertiesToAssign, position); + } + + private static void ApplyPropertiesTo(PositionedObject entity, List propertiesToAssign, Microsoft.Xna.Framework.Vector3 position) + { + if (entity != null) + { + entity.Position = position; + } + + var entityType = entity.GetType(); + var lateBinder = Instructions.Reflection.LateBinder.GetInstance(entityType); + + foreach (var property in propertiesToAssign) + { + // If name is EntityToCreate, skip it: + string propertyName = property.Name; + + bool shouldSet = propertyName != "EntityToCreate"; + + if (shouldSet) + { + if (propertyName == "name") + { + propertyName = "Name"; + } + + var valueToSet = property.Value; + + var propertyType = property.Type; + + if (string.IsNullOrEmpty(propertyType)) + { + propertyType = TryGetPropertyType(entityType, propertyName); + } + + valueToSet = SetValueAccordingToType(valueToSet, propertyName, propertyType, entityType); + try + { + lateBinder.SetValue(entity, propertyName, valueToSet); + } + catch (InvalidCastException e) + { + string assignedType = valueToSet.GetType().ToString() ?? "unknown type"; + assignedType = GetFriendlyNameForType(assignedType); + + string expectedType = "unknown type"; + object outValue; + if (lateBinder.TryGetValue(entity, propertyName, out outValue) && outValue != null) + { + expectedType = outValue.GetType().ToString(); + expectedType = GetFriendlyNameForType(expectedType); + } + + // This means that the property exists but is of a different type. + string message = $"Attempted to assign the property {propertyName} " + + $"to a value of type {assignedType} but expected {expectedType}. " + + $"Check the property type in your TMX and make sure it matches the type on the entity."; + throw new Exception(message, e); + } + catch (Exception e) + { + // Since this code indiscriminately tries to set properties, it may set properties which don't + // actually exist. Therefore, we tolerate failures. + } + } + } + } + + private static string TryGetPropertyType(Type entityType, string propertyName) + { + // todo - cache for perf + var property = entityType.GetProperty(propertyName); + + if (property != null) + { + return property?.PropertyType.FullName; + } + else + { + var field = entityType.GetField(propertyName); + return field?.FieldType.FullName; + } + } + + public static void RegisterDictionary(Dictionary data) + { +#if DEBUG + if(data == null) + { + throw new ArgumentNullException("The argument data is null - do you need to call LoadStaticContent on the type containing this dictionary?"); + } +#endif + + var type = typeof(T).FullName; + + if (allDictionaries.ContainsKey(type) == false) + { + allDictionaries.Add(type, new List()); + } + + if (allDictionaries[type].Contains(data) == false) + { + allDictionaries[type].Add(data); + } + } + + private static string GetFriendlyNameForType(string type) + { + switch (type) + { + case "System.String": return "string"; + case "System.Single": return "float"; + + } + return type; + } + + + private static object SetValueAccordingToType(object valueToSet, string valueName, string valueType, Type entityType) + { + if (valueType == "bool") + { + bool boolValue = false; + + if (bool.TryParse((string)valueToSet, out boolValue)) + { + valueToSet = boolValue; + } + } + else if (valueType == "float") + { + float floatValue; + + if (float.TryParse((string)valueToSet, System.Globalization.NumberStyles.Float, System.Globalization.NumberFormatInfo.InvariantInfo, out floatValue)) + { + valueToSet = floatValue; + } + } + else if (valueType == "int") + { + int intValue; + + if (int.TryParse((string)valueToSet, out intValue)) + { + valueToSet = intValue; + } + } + else if (valueName == "CurrentState") + { + // Since it's part of the class, it uses the "+" separator + var enumTypeName = entityType.FullName + "+VariableState"; + var enumType = typesInThisAssembly.FirstOrDefault(item => item.FullName == enumTypeName); + + valueToSet = Enum.Parse(enumType, (string)valueToSet); + } + else if (valueType != null && allDictionaries.ContainsKey(valueType)) + { + var list = allDictionaries[valueType]; + + foreach (var dictionary in list) + { + if (dictionary.Contains(valueToSet)) + { + valueToSet = dictionary[valueToSet]; + break; + } + } + } + // If this has a + in it, then that might mean it's a state. We should try to get the type, and if we find it, stuff + // it in allDictionaries to make future calls faster + else if (valueType != null && valueType.Contains("+")) + { + var stateType = typesInThisAssembly.FirstOrDefault(item => item.FullName == valueType); + + if (stateType != null) + { + Dictionary allValues = new Dictionary(); + + var fields = stateType.GetFields(BindingFlags.Static | BindingFlags.Public); + + foreach (var field in fields) + { + allValues[field.Name] = field.GetValue(null); + } + + // The list has all the dictioanries that contain values. But for states there is only one set of values, so + // we create a list + List list = new List(); + list.Add(allValues); + + allDictionaries[valueType] = list; + + if (allValues.ContainsKey((string)valueToSet)) + { + valueToSet = allValues[(string)valueToSet]; + } + } + + } + return valueToSet; + } + + + private static void AssignCustomPropertyTo(PositionedObject entity, NamedValue property) + { + throw new NotImplementedException(); + } + + + static Type[] typesInThisAssembly; + private static IEntityFactory GetFactory(string entityType) + { + if (typesInThisAssembly == null) + { +#if WINDOWS_8 || UWP + var assembly = typeof(TileEntityInstantiator).GetTypeInfo().Assembly; + typesInThisAssembly = assembly.DefinedTypes.Select(item=>item.AsType()).ToArray(); + +#else + var assembly = Assembly.GetExecutingAssembly(); + typesInThisAssembly = assembly.GetTypes(); +#endif + } + + +#if WINDOWS_8 || UWP + var filteredTypes = + typesInThisAssembly.Where(t => t.GetInterfaces().Contains(typeof(IEntityFactory)) + && t.GetConstructors().Any(c=>c.GetParameters().Count() == 0)); +#else + var filteredTypes = + typesInThisAssembly.Where(t => t.GetInterfaces().Contains(typeof(IEntityFactory)) + && t.GetConstructor(Type.EmptyTypes) != null); +#endif + + var factories = filteredTypes + .Select( + t => + { +#if WINDOWS_8 || UWP + var propertyInfo = t.GetProperty("Self"); +#else + var propertyInfo = t.GetProperty("Self"); +#endif + var value = propertyInfo.GetValue(null, null); + return value as IEntityFactory; + }).ToList(); + + + var factory = factories.FirstOrDefault(item => + { + var type = item.GetType(); + var methodInfo = type.GetMethod("CreateNew", new[] { typeof(Layer), typeof(float), typeof(float) }); + var returntypeString = methodInfo.ReturnType.Name; + + return entityType == returntypeString || entityType.EndsWith("\\" + returntypeString); + }); + return factory; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/AbstractMapLayer.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/AbstractMapLayer.cs new file mode 100644 index 000000000..42bfacf6d --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/AbstractMapLayer.cs @@ -0,0 +1,17 @@ +using System; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ +#if !UWP + [Serializable] +#endif + [XmlInclude(typeof (MapLayer))] + [XmlInclude(typeof (MapImageLayer))] + [XmlInclude(typeof (mapObjectgroup))] + public abstract class AbstractMapLayer + { + [XmlAttribute("name")] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/AnimationChainContainer.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/AnimationChainContainer.cs new file mode 100644 index 000000000..93f1ecab6 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/AnimationChainContainer.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Graphics.Animation; +using Microsoft.Xna.Framework.Graphics; +using FlatRedBall.Math; + +namespace FlatRedBall.TileGraphics +{ + /// + /// An AnimationChain container that self animates. This can be used + /// as the container for IAnimationChainAnimatables. + /// + public class AnimationChainContainer + { + double mTimeIntoAnimation = 0; + + public float AnimationSpeed + { + get; + set; + } + + public int CurrentFrameIndex + { + get; + set; + } + + public AnimationFrame CurrentFrame + { + get + { + return AnimationChain[CurrentFrameIndex]; + } + } + + public AnimationChain AnimationChain + { + get; + set; + } + + public Texture2D CurrentTexture + { + get + { + return AnimationChain[CurrentFrameIndex].Texture; + } + } + + public AnimationChainContainer(AnimationChain animationChain) + { + this.AnimationChain = animationChain; + AnimationSpeed = 1; + CurrentFrameIndex = 1; + } + + /// + /// Moves the animation forward by the argument time; + /// + /// The amount of time that has passed since last update. + public void Activity(float secondDifference) + { + if (AnimationChain != null) + { + double modifiedTimePassed = TimeManager.SecondDifference * AnimationSpeed; + + mTimeIntoAnimation += modifiedTimePassed; + + mTimeIntoAnimation = MathFunctions.Loop(mTimeIntoAnimation, AnimationChain.TotalLength); + + UpdateFrameBasedOffOfTimeIntoAnimation(); + } + } + + void UpdateFrameBasedOffOfTimeIntoAnimation() + { + double timeIntoAnimation = mTimeIntoAnimation; + + if (timeIntoAnimation < 0) + { + throw new ArgumentException("The timeIntoAnimation argument must be 0 or positive"); + } + else if (AnimationChain != null && AnimationChain.Count > 1) + { + int frameIndex = 0; + + // Don't start the while loop if the animation + // has a length of 0, will result in an infinite loop + if (AnimationChain.TotalLength > 0) + { + while (timeIntoAnimation >= 0) + { + double frameTime = AnimationChain[frameIndex].FrameLength; + if (timeIntoAnimation < frameTime) + { + CurrentFrameIndex = frameIndex; + + break; + } + else + { + timeIntoAnimation -= frameTime; + + frameIndex = (frameIndex + 1) % AnimationChain.Count; + } + } + } + } + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/ExternalTileset.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ExternalTileset.cs new file mode 100644 index 000000000..1da1d590f --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ExternalTileset.cs @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.5448 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Xml.Serialization; +using System.Collections.Generic; +using TMXGlueLib; + +// +// This source code was auto-generated by xsd, Version=2.0.50727.3038. +// + + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +[XmlRoot("tileset")] +public partial class tileset +{ + + private tilesetImage[] imageField; + + private string nameField; + + private int tilewidthField; + + private int tileheightField; + + private int spacingField; + + private int marginField; + [System.Xml.Serialization.XmlElementAttribute("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public List tile = new List(); + //{ + // get + // { + // return this.tileField; + // } + // set + // { + // if (this.tileField != null && this.tileField.Length > 0) + // { + // return; + // } + // else + // { + // this.tileField = value; + // } + // } + //} + + /// + [System.Xml.Serialization.XmlElementAttribute("image", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] + public tilesetImage[] image { + get { + return this.imageField; + } + set { + this.imageField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string name { + get { + return this.nameField; + } + set { + this.nameField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int tilewidth { + get { + return this.tilewidthField; + } + set { + this.tilewidthField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int tileheight { + get { + return this.tileheightField; + } + set { + this.tileheightField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int spacing { + get { + return this.spacingField; + } + set { + this.spacingField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int margin { + get { + return this.marginField; + } + set { + this.marginField = value; + } + } +} + + +[System.Xml.Serialization.XmlRootAttribute(ElementName = "tileset", Namespace = "", IsNullable = false)] +public class ExternalTileSet : Tileset +{ + + +} + + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +public partial class tilesetImage { + + private string sourceField; + + private int widthField; + + private int heightField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string source { + get { + return this.sourceField; + } + set { + this.sourceField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int width { + get { + return this.widthField; + } + set { + this.widthField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public int height { + get { + return this.heightField; + } + set { + this.heightField = value; + } + } +} + +/// +[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] +[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] +public partial class NewDataSet { + + private tileset[] itemsField; + + /// + [System.Xml.Serialization.XmlElementAttribute("tileset")] + public tileset[] Items { + get { + return this.itemsField; + } + set { + this.itemsField = value; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMap.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMap.cs new file mode 100644 index 000000000..147dc50b0 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMap.cs @@ -0,0 +1,1031 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Content.Scene; +using FlatRedBall.Content; +using FlatRedBall.IO; +using FlatRedBall.Debugging; +using FlatRedBall.Performance.Measurement; +using System.IO; +using TMXGlueLib.DataTypes; +using TMXGlueLib; +using FlatRedBall.Graphics; +using FlatRedBall.Graphics.Animation; +using FlatRedBall.Math.Geometry; + +namespace FlatRedBall.TileGraphics +{ + + + public partial class LayeredTileMap : PositionedObject, IVisible + { + #region Fields + FlatRedBall.Math.PositionedObjectList mMapLists = new FlatRedBall.Math.PositionedObjectList(); + + float mRenderingScale = 1; + + float mZSplit = 1; + #endregion + + #region Properties + public float? NumberTilesWide { get; private set; } + public float? NumberTilesTall { get; private set; } + public float? WidthPerTile { get; private set; } + public float? HeightPerTile { get; private set; } + public Dictionary> TileProperties + { + get; + private set; + } = new Dictionary>(); + + + public Dictionary> ShapeProperties + { + get; + private set; + } = new Dictionary>(); + + + public float RenderingScale + { + get + { + return mRenderingScale; + } + set + { + mRenderingScale = value; + foreach (var map in mMapLists) + { + map.RenderingScale = value; + } + + } + } + + public float ZSplit + { + get + { + return mZSplit; + } + set + { + for (int i = 0; i < this.mMapLists.Count; i++) + { + mMapLists[i].RelativeZ = mZSplit * i; + } + } + } + + public List ShapeCollections { get; private set; } = new List(); + + + public FlatRedBall.Math.PositionedObjectList MapLayers + { + get + { + return mMapLists; + } + } + + public List Collisions + { get; private set; } = new List(); + + + bool visible = true; + public bool Visible + { + get + { + return visible; + } + set + { + visible = value; + foreach (var item in this.mMapLists) + { + item.Visible = visible; + } + } + } + + /// + /// Returns the width in world units of the entire map. The map may not visibly extend to this width; + /// + public float Width + { + get + { + if (NumberTilesWide.HasValue && WidthPerTile.HasValue) + { + return NumberTilesWide.Value * WidthPerTile.Value; + } + else + { + return 0; + } + } + } + + /// + /// Returns the height in world units of the entire map. The map may not visibly extend to this height; + /// + public float Height + { + get + { + if (NumberTilesTall.HasValue && HeightPerTile.HasValue) + { + return NumberTilesTall.Value * HeightPerTile.Value; + } + else + { + return 0; + } + } + } + + public LayeredTileMapAnimation Animation { get; set; } + + public List MapProperties { get; set; } + + + IVisible IVisible.Parent + { + get + { + return Parent as IVisible; + } + } + + public bool AbsoluteVisible + { + get + { + if (this.Visible) + { + var parentAsIVisible = this.Parent as IVisible; + + if (parentAsIVisible == null || IgnoresParentVisibility) + { + return true; + } + else + { + // this is true, so return if the parent is visible: + return parentAsIVisible.AbsoluteVisible; + } + } + else + { + return false; + } + } + } + + public bool IgnoresParentVisibility + { + get; + set; + } + + + #endregion + + public IEnumerable TileNamesWith(string propertyName) + { + foreach (var item in TileProperties.Values) + { + if (item.Any(item2 => item2.Name == propertyName)) + { + var hasName = item.Any(item2 => item2.Name == "Name"); + + if (hasName) + { + yield return item.First(item2 => item2.Name == "Name").Value as string; + } + } + } + } + + + public static LayeredTileMap FromScene(string fileName, string contentManagerName) + { + return FromScene(fileName, contentManagerName, false); + } + + public static LayeredTileMap FromScene(string fileName, string contentManagerName, bool verifySameTexturePerLayer) + { + Section.GetAndStartContextAndTime("Initial FromScene"); + LayeredTileMap toReturn = new LayeredTileMap(); + + string absoluteFileName = FileManager.MakeAbsolute(fileName); + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("FromFile"); + SceneSave ses = SceneSave.FromFile(absoluteFileName); + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("BreaksNStuff"); + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(absoluteFileName); + + var breaks = GetZBreaks(ses.SpriteList); + + int valueBefore = 0; + + MapDrawableBatch mdb; + int valueAfter; + + float zValue = 0; + Section.EndContextAndTime(); + Section.GetAndStartContextAndTime("Create MDBs"); + + for (int i = 0; i < breaks.Count; i++) + { + valueAfter = breaks[i]; + + int count = valueAfter - valueBefore; + + mdb = MapDrawableBatch.FromSpriteSaves(ses.SpriteList, valueBefore, count, contentManagerName, verifySameTexturePerLayer); + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = zValue; + toReturn.mMapLists.Add(mdb); + zValue += toReturn.mZSplit; + valueBefore = valueAfter; + } + + valueAfter = ses.SpriteList.Count; + if (valueBefore != valueAfter) + { + int count = valueAfter - valueBefore; + + mdb = MapDrawableBatch.FromSpriteSaves(ses.SpriteList, valueBefore, count, contentManagerName, verifySameTexturePerLayer); + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = zValue; + + toReturn.mMapLists.Add(mdb); + } + Section.EndContextAndTime(); + FileManager.RelativeDirectory = oldRelativeDirectory; + return toReturn; + } + + public static LayeredTileMap FromReducedTileMapInfo(string fileName, string contentManagerName) + { + using (Stream inputStream = FileManager.GetStreamForFile(fileName)) + using (BinaryReader binaryReader = new BinaryReader(inputStream)) + { + ReducedTileMapInfo rtmi = ReducedTileMapInfo.ReadFrom(binaryReader); + + + + string fullFileName = fileName; + if (FileManager.IsRelative(fullFileName)) + { + fullFileName = FileManager.RelativeDirectory + fileName; + } + + var toReturn = FromReducedTileMapInfo(rtmi, contentManagerName, fileName); + toReturn.Name = fullFileName; + return toReturn; + } + } + + public static LayeredTileMap FromReducedTileMapInfo(TMXGlueLib.DataTypes.ReducedTileMapInfo rtmi, string contentManagerName, string tilbToLoad) + { + var toReturn = new LayeredTileMap(); + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(tilbToLoad); + + MapDrawableBatch mdb; + + if (rtmi.NumberCellsWide != 0) + { + toReturn.NumberTilesWide = rtmi.NumberCellsWide; + } + + if (rtmi.NumberCellsTall != 0) + { + toReturn.NumberTilesTall = rtmi.NumberCellsTall; + } + + toReturn.WidthPerTile = rtmi.QuadWidth; + toReturn.HeightPerTile = rtmi.QuadHeight; + + for (int i = 0; i < rtmi.Layers.Count; i++) + { + var reducedLayer = rtmi.Layers[i]; + + mdb = MapDrawableBatch.FromReducedLayer(reducedLayer, toReturn, rtmi, contentManagerName); + + mdb.AttachTo(toReturn, false); + mdb.RelativeZ = reducedLayer.Z; + toReturn.mMapLists.Add(mdb); + + } + FileManager.RelativeDirectory = oldRelativeDirectory; + + return toReturn; + } + + public static LayeredTileMap FromTiledMapSave(string fileName, string contentManager) + { + TiledMapSave tms = TiledMapSave.FromFile(fileName); + + // Ultimately properties are tied to tiles by the tile name. + // If a tile has no name but it has properties, those properties + // will be lost in the conversion. Therefore, we have to add name properties. + tms.MoveTypeToProperties(); + +#if DEBUG + CheckForDuplicateTilesets(tms); +#endif + + tms.NameUnnamedTilesetTiles(); + tms.NameUnnamedObjects(); + + + string directory = FlatRedBall.IO.FileManager.GetDirectory(fileName); + + var rtmi = ReducedTileMapInfo.FromTiledMapSave( + tms, 1, 0, directory, FileReferenceType.Absolute); + + var toReturn = FromReducedTileMapInfo(rtmi, contentManager, fileName); + + AddShapeCollections(toReturn, tms); + + foreach (var layer in tms.MapLayers) + { + var matchingLayer = toReturn.MapLayers.FirstOrDefault(item => item.Name == layer.Name); + + + if (matchingLayer != null) + { + if (layer is MapLayer) + { + var mapLayer = layer as MapLayer; + foreach (var propertyValues in mapLayer.properties) + { + matchingLayer.Properties.Add(new NamedValue + { + Name = propertyValues.StrippedName, + Value = propertyValues.value, + Type = propertyValues.Type + }); + } + + matchingLayer.Visible = mapLayer.visible == 1; + } + } + } + + + foreach (var tileset in tms.Tilesets) + { + foreach (var tile in tileset.TileDictionary.Values) + { + int propertyCountFromTileset = 0; + + if (tile.properties.Count != 0) + { + // this needs a name: + string name = tile.properties.FirstOrDefault(item => item.StrippedName.ToLowerInvariant() == "name")?.value; + // todo - eventually need to copy default values from the Tileset to the tile here + AddPropertiesToMap(tms, toReturn.TileProperties, tile.properties, null, name); + } + } + } + + foreach (var objectLayer in tms.objectgroup) + { + if (objectLayer.@object != null) + { + foreach (var objectInstance in objectLayer.@object) + { + TMXGlueLib.Tileset tileset = null; + int propertyCountFromTileset = 0; + + var objectProperties = objectInstance.properties; + List tilesetProperties = null; + if (objectInstance.gid != null) + { + tileset = tms.GetTilesetForGid(objectInstance.gid.Value); + if (tileset.TileDictionary.ContainsKey(objectInstance.gid.Value - tileset.Firstgid)) + { + tilesetProperties = tileset.TileDictionary[objectInstance.gid.Value - tileset.Firstgid].properties; + propertyCountFromTileset = tilesetProperties.Count; + } + } + + + if (objectProperties.Count + propertyCountFromTileset != 0) + { + string name = objectInstance.Name; + // if name is null, check the properties: + if (string.IsNullOrEmpty(name)) + { + name = objectProperties.FirstOrDefault(item => item.StrippedNameLower == "name")?.value; + } + + var objectInstanceIsTile = objectInstance.gid != null; + + if (objectInstanceIsTile) + { + AddPropertiesToMap(tms, toReturn.TileProperties, objectProperties, tilesetProperties, name); + } + else + { + AddPropertiesToMap(tms, toReturn.ShapeProperties, objectProperties, tilesetProperties, name); + } + } + } + } + } + + var tmxDirectory = FileManager.GetDirectory(fileName); + + // add image layers + foreach (var imageLayer in tms.ImageLayers) + { + var imageLayerFile = tmxDirectory + imageLayer.ImageObject.Source; + var texture = FlatRedBallServices.Load(imageLayerFile); + + var newSprite = new Sprite + { + Texture = texture, + Width = imageLayer.ImageObject.Width, + Height = imageLayer.ImageObject.Height, + X = imageLayer.ImageObject.Width / 2 + imageLayer.OffsetX, + Y = -imageLayer.ImageObject.Height / 2 + imageLayer.OffsetY + }; + + var mdb = new MapDrawableBatch(1, texture); + mdb.AttachTo(toReturn, false); + mdb.Paste(newSprite); + mdb.Visible = imageLayer.Visible; + + toReturn.mMapLists.Add(mdb); + } + + var animationDictionary = new Dictionary(); + + // add animations + foreach (var tileset in tms.Tilesets) + { + + string tilesetImageFile = tmxDirectory + tileset.Images[0].Source; + + if (tileset.SourceDirectory != ".") + { + tilesetImageFile = tmxDirectory + tileset.SourceDirectory + tileset.Images[0].Source; + } + + var texture = FlatRedBallServices.Load(tilesetImageFile); + + foreach (var tile in tileset.Tiles.Where(item => item.Animation != null && item.Animation.Frames.Count != 0)) + { + var animation = tile.Animation; + + var animationChain = new AnimationChain(); + foreach (var frame in animation.Frames) + { + var animationFrame = new AnimationFrame(); + animationFrame.FrameLength = frame.Duration / 1000.0f; + animationFrame.Texture = texture; + + int tileIdRelative = frame.TileId; + int globalTileId = (int)(tileIdRelative + tileset.Firstgid); + + int leftPixel; + int rightPixel; + int topPixel; + int bottomPixel; + TiledMapSave.GetPixelCoordinatesFromGid((uint)globalTileId, tileset, out leftPixel, out topPixel, out rightPixel, out bottomPixel); + + animationFrame.LeftCoordinate = MapDrawableBatch.CoordinateAdjustment + leftPixel / (float)texture.Width; + animationFrame.RightCoordinate = -MapDrawableBatch.CoordinateAdjustment + rightPixel / (float)texture.Width; + + animationFrame.TopCoordinate = MapDrawableBatch.CoordinateAdjustment + topPixel / (float)texture.Height; + animationFrame.BottomCoordinate = -MapDrawableBatch.CoordinateAdjustment + bottomPixel / (float)texture.Height; + + + animationChain.Add(animationFrame); + } + + var property = tile.properties.FirstOrDefault(item => item.StrippedNameLower == "name"); + + if (property == null) + { + throw new InvalidOperationException( + $"The tile with ID {tile.id} has an animation, but it doesn't have a Name property, which is required for animation."); + } + else + { + animationDictionary.Add(property.value, animationChain); + } + + } + + } + + toReturn.Animation = new LayeredTileMapAnimation(animationDictionary); + + AddTileShapeCollections(toReturn, tms); + + + toReturn.MapProperties = tms.properties + .Select(propertySave => new NamedValue + { Name = propertySave.name, Value = propertySave.value, Type = propertySave.Type }) + .ToList(); + + + return toReturn; + } + + /// + /// Throws an exception if the same tileset is added to the TiledMapSave twice - this can cause duplicate names in unnamed tiles. + /// + private static void CheckForDuplicateTilesets(TiledMapSave tiledMapSave) + { + foreach (var tileset in tiledMapSave.Tilesets) + { + if (!string.IsNullOrEmpty(tileset.Source)) + { + var duplicateExists = tiledMapSave.Tilesets.Any(other => other != tileset && other.Source == tileset.Source); + + if (duplicateExists) + { + throw new Exception($"The tileset {tileset.Source} is referenced in the .tmx file {tiledMapSave.FileName} more than once. This can cause tile name conflicts."); + } + } + } + } + + private static void AddTileShapeCollections(LayeredTileMap layeredTileMap, TiledMapSave tms) + { + var allTilesets = tms.Tilesets; + + Dictionary hasShapesDictionary = new Dictionary(); + + Dictionary nameCollisionPairs = new Dictionary(); + + + for (int i = 0; i < tms.Layers.Count; i++) + { + var layer = tms.Layers[i]; + // Currently we only support 1 tileset per layer, so we'll find the tileset for this layer + var firstNonZero = layer.data[0].tiles.FirstOrDefault(item => item != 0); + + if (firstNonZero != 0) + { + var tileset = tms.GetTilesetForGid(firstNonZero); + + if (tileset != null) + { + bool hasShapes; + if (hasShapesDictionary.ContainsKey(tileset)) + { + hasShapes = hasShapesDictionary[tileset]; + } + else + { + // We don't know if this tileset has shapes yet, so let's figure it out: + hasShapes = tileset.Tiles.Any(item => item.Objects?.@object?.Length > 0); + + hasShapesDictionary[tileset] = hasShapes; + } + + if (hasShapes) + { + AddTileShapeCollectionForLayer(layer, nameCollisionPairs, tileset, tms.tilewidth, i); + + } + } + } + } + foreach (var item in nameCollisionPairs.Values) + { + var sortOnY = layeredTileMap.Height > layeredTileMap.Width; + if (sortOnY) + { + item.SortAxis = Math.Axis.Y; + } + else + { + item.SortAxis = Math.Axis.X; + } + + item.RefreshAllRepositionDirections(); + + layeredTileMap.Collisions.Add(item); + } + } + + private static TileCollisions.TileShapeCollection GetOrAddTileShapeCollection(string name, Dictionary collisionDictionary) + { + if (collisionDictionary.ContainsKey(name)) + { + return collisionDictionary[name]; + } + else + { + var collection = new TileCollisions.TileShapeCollection(); + collection.Name = name; + + collisionDictionary[name] = collection; + + return collection; + } + } + + private static void AddTileShapeCollectionForLayer(TMXGlueLib.MapLayer layer, Dictionary collisionDictionary, TMXGlueLib.Tileset tileset, float tileDimension, float z) + { + Math.Geometry.AxisAlignedRectangle rectangle = null; + Math.Geometry.Polygon polygon = null; + Circle circle; + + var tiles = layer.data[0].tiles; + + + bool sortOnY = layer.height > layer.width; + + + foreach (var tilesetTile in tileset.Tiles.Where(item => item.Objects?.@object?.Length > 0)) + { + var tilesetTileGid = tilesetTile.id + tileset.Firstgid; + foreach (var tilesetObject in tilesetTile.Objects.@object) + { + + TiledMapToShapeCollectionConverter.ConvertTiledObjectToFrbShape(tilesetObject, out polygon, out rectangle, out circle); + if (rectangle != null) + { + TileCollisions.TileShapeCollection collection = null; + rectangle.Z = z; + if (sortOnY) + { + for (int y = 0; y < layer.height; y++) + { + for (int x = 0; x < layer.width; x++) + { + AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + else + { + for (int x = 0; x < layer.width; x++) + { + for (int y = 0; y < layer.height; y++) + { + AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + } + else if (polygon != null) + { + TileCollisions.TileShapeCollection collection = null; + + // For tile polygons we want them to be centered on the tile. + // To do this, we shift all points by its position: + for (int i = 0; i < polygon.Points.Count; i++) + { + var point = polygon.Points[i]; + point.X += polygon.Position.X - tileDimension / 2.0f; + point.Y += polygon.Position.Y + tileDimension / 2.0f; + + polygon.SetPoint(i, point); + + } + + polygon.Z = z; + + if (sortOnY) + { + for (int y = 0; y < layer.height; y++) + { + for (int x = 0; x < layer.width; x++) + { + AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + else + { + for (int x = 0; x < layer.width; x++) + { + for (int y = 0; y < layer.height; y++) + { + AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, x, y, collisionDictionary, ref collection); + } + } + } + + } + else if (circle != null) + { + throw new NotImplementedException("Need to handle circles..."); + } + } + } + + } + + private static void AddPolygonCloneAtXY(MapLayer layer, float tileDimension, Polygon polygon, List tiles, long tilesetTileGid, int x, int y, + Dictionary dictionary, ref TileCollisions.TileShapeCollection collectionForThisName) + { + var i = y * layer.width + x; + + if (tiles[i] == tilesetTileGid) + { + if (collectionForThisName == null) + { + collectionForThisName = GetOrAddTileShapeCollection(polygon.Name ?? layer.Name, dictionary); + } + int xIndex = i % layer.width; + // intentional int division + int yIndex = i / layer.width; + + var cloned = polygon.Clone(); + + cloned.X = (xIndex + .5f) * tileDimension; + cloned.Y = -(yIndex + .5f) * tileDimension; + + collectionForThisName.Polygons.Add(cloned); + } + } + + private static void AddRectangleCloneAtXY(MapLayer layer, float tileDimension, AxisAlignedRectangle rectangle, List tiles, long tilesetTileGid, int x, int y, + Dictionary dictionary, ref TileCollisions.TileShapeCollection collectionForThisName) + { + var i = y * layer.width + x; + + var stripedId = tiles[i] & 0x0fffffff; + + if (stripedId == tilesetTileGid) + { + if (collectionForThisName == null) + { + collectionForThisName = GetOrAddTileShapeCollection(rectangle.Name ?? layer.Name, dictionary); + } + + float xIndex = i % layer.width; + // intentional int division + float yIndex = i / layer.width; + + var cloned = rectangle.Clone(); + + // Offset by .5 since polygons are positioned by their center, tiles by top left + cloned.X = (xIndex + .5f) * tileDimension; + cloned.Y = -(yIndex + .5f) * tileDimension; + + collectionForThisName.Rectangles.Add(cloned); + + } + } + + private static void AddShapeCollections(LayeredTileMap layeredTileMap, TiledMapSave tms) + { + foreach (var mapObjectgroup in tms.objectgroup) + { + int indexInAllLayers = tms.MapLayers.IndexOf(mapObjectgroup); + + var shapeCollection = tms.ToShapeCollection(mapObjectgroup.Name); + if (shapeCollection != null && shapeCollection.IsEmpty == false) + { + // This makes all shapes have the same Z as the index layer, which is useful if instantiating objects, so they're layered properly + shapeCollection.Shift(new Microsoft.Xna.Framework.Vector3(0, 0, indexInAllLayers)); + + shapeCollection.Name = mapObjectgroup.Name; + layeredTileMap.ShapeCollections.Add(shapeCollection); + } + } + } + + private static void AddPropertiesToMap(TiledMapSave tms, Dictionary> dictionaryToAddTo, List objectProperties, List tilesetProperties, string name) + { + if (!string.IsNullOrEmpty(name)) + { + List namedValues = new List(); + foreach (var prop in objectProperties) + { + namedValues.Add(new NamedValue() + { Name = prop.StrippedName, Value = prop.value, Type = prop.Type }); + } + + if (tilesetProperties != null) + { + foreach (var tilesetProperty in tilesetProperties) + { + bool hasAlreadyBeenAdded = objectProperties.Any(item => item.StrippedNameLower == tilesetProperty.StrippedNameLower); + + if (!hasAlreadyBeenAdded) + { + namedValues.Add(new NamedValue() + { Name = tilesetProperty.StrippedName, Value = tilesetProperty.value, Type = tilesetProperty.Type }); + } + + } + } + +#if DEBUG + if (dictionaryToAddTo.Any(item => item.Key == name)) + { + // Assume it was a duplicate tile name, but it may not be + string message = $"The tileset contains more than one tile with the name {name}. Names must be unique in a tileset."; + bool hasDuplicateObject = tms.objectgroup.Any(item => item.@object.Any(objectInstance => objectInstance.Name == name)); + if (hasDuplicateObject) + { + message = $"The tileset contains a tile with the name {name}, but this name is already used in an object layer"; + } + throw new InvalidOperationException(message); + } +#endif + + dictionaryToAddTo.Add(name, namedValues); + + } + } + + + + public void AnimateSelf() + { + if (Animation != null) + { + Animation.Activity(this); + } + } + + public void AddToManagers() + { + AddToManagers(null); + } + + public void AddToManagers(FlatRedBall.Graphics.Layer layer) + { + bool isAlreadyManaged = SpriteManager.ManagedPositionedObjects + .Contains(this); + + // This allows AddToManagers to be called multiple times, so it can be added to multiple layers + if (!isAlreadyManaged) + { + SpriteManager.AddPositionedObject(this); + } + foreach (var item in this.mMapLists) + { + item.AddToManagers(layer); + } + } + + //Write some addtomanagers and remove methods + + static List GetZBreaks(List list) + { + List zBreaks = new List(); + + GetZBreaks(list, zBreaks); + + return zBreaks; + + } + + static void GetZBreaks(List list, List zBreaks) + { + zBreaks.Clear(); + + if (list.Count == 0 || list.Count == 1) + return; + + for (int i = 1; i < list.Count; i++) + { + if (list[i].Z != list[i - 1].Z) + zBreaks.Add(i); + } + } + + public LayeredTileMap Clone() + { + var toReturn = base.Clone(); + + toReturn.mMapLists = new Math.PositionedObjectList(); + + foreach (var item in this.MapLayers) + { + var clonedLayer = item.Clone(); + if (item.Parent == this) + { + clonedLayer.AttachTo(toReturn, false); + } + toReturn.mMapLists.Add(clonedLayer); + } + + toReturn.ShapeCollections = new List(); + foreach (var shapeCollection in this.ShapeCollections) + { + toReturn.ShapeCollections.Add(shapeCollection.Clone()); + } + + return toReturn; + } + + public void RemoveFromManagersOneWay() + { + this.mMapLists.MakeOneWay(); + for (int i = 0; i < mMapLists.Count; i++) + { + SpriteManager.RemoveDrawableBatch(this.mMapLists[i]); + } + + SpriteManager.RemovePositionedObject(this); + + for (int i = 0; i < this.Collisions.Count; i++) + { + this.Collisions[i].RemoveFromManagersOneWay(); + } + + this.mMapLists.MakeTwoWay(); + } + + public void Destroy() + { + // Make it one-way because we want the + // contained objects to persist after a destroy + mMapLists.MakeOneWay(); + + for (int i = 0; i < mMapLists.Count; i++) + { + SpriteManager.RemoveDrawableBatch(mMapLists[i]); + } + + for (int i = 0; i < this.Collisions.Count; i++) + { + this.Collisions[i].RemoveFromManagers(); + } + + for (int i = 0; i < this.ShapeCollections.Count; i++) + { + this.ShapeCollections[i].RemoveFromManagers(); + } + + SpriteManager.RemovePositionedObject(this); + + mMapLists.MakeTwoWay(); + } + } + + + + public static class LayeredTileMapExtensions + { + public static void RemoveTiles(this LayeredTileMap map, + Func, bool> predicate, + Dictionary> Properties) + { + // Force execution now for performance reasons + var filteredInfos = Properties.Values.Where(predicate).ToList(); + + foreach (var layer in map.MapLayers) + { + RemoveTilesFromLayer(filteredInfos, layer); + } + } + + public static void RemoveTiles(this MapDrawableBatch layer, + Func, bool> predicate, + Dictionary> Properties) + { + // Force execution now for performance reasons + var filteredInfos = Properties.Values.Where(predicate).ToList(); + + RemoveTilesFromLayer(filteredInfos, layer); + } + + private static void RemoveTilesFromLayer(List> filteredInfos, MapDrawableBatch layer) + { + List indexes = new List(); + + foreach (var itemThatPasses in filteredInfos) + { + string tileName = itemThatPasses + .FirstOrDefault(item => item.Name.ToLowerInvariant() == "name") + .Value as string; + + + if (layer.NamedTileOrderedIndexes.ContainsKey(tileName)) + { + var intsOnThisLayer = + layer.NamedTileOrderedIndexes[tileName]; + + indexes.AddRange(intsOnThisLayer); + } + } + + + layer.RemoveQuads(indexes); + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMapAnimation.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMapAnimation.cs new file mode 100644 index 000000000..6da25ae1d --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/LayeredTileMapAnimation.cs @@ -0,0 +1,63 @@ +using FlatRedBall.Graphics.Animation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FlatRedBall.TileGraphics +{ + public partial class LayeredTileMapAnimation + { + Dictionary mAnimationChainContainers = new Dictionary(); + + public LayeredTileMapAnimation(Dictionary animationChainAssociations) + { + foreach(var kvp in animationChainAssociations) + { + AnimationChainContainer container = new AnimationChainContainer(kvp.Value); + + mAnimationChainContainers.Add(kvp.Key, container); + + } + } + + public void Activity(LayeredTileMap layeredTileMap) + { + foreach (var kvp in mAnimationChainContainers) + { + AnimationChainContainer container = kvp.Value; + + + int indexBefore = container.CurrentFrameIndex; + container.Activity(TimeManager.SecondDifference); + if (container.CurrentFrameIndex != indexBefore) + { + ReactToChangedAnimationFrame(kvp.Key, kvp.Value, layeredTileMap); + + } + } + } + + + private void ReactToChangedAnimationFrame(string spriteName, AnimationChainContainer animationChainContainer, LayeredTileMap layeredTileMap) + { + AnimationFrame animationFrame = animationChainContainer.CurrentFrame; + + foreach (var mapLayer in layeredTileMap.MapLayers) + { + var nameDictionary = mapLayer.NamedTileOrderedIndexes; + + if (nameDictionary.ContainsKey(spriteName)) + { + var indexes = nameDictionary[spriteName]; + + foreach (int value in indexes) + { + mapLayer.PaintTileTextureCoordinates(value, animationFrame.LeftCoordinate, animationFrame.TopCoordinate, + animationFrame.RightCoordinate, animationFrame.BottomCoordinate); + } + } + } + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapDrawableBatch.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapDrawableBatch.cs new file mode 100644 index 000000000..c2ecf620f --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapDrawableBatch.cs @@ -0,0 +1,1386 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Graphics; +using Microsoft.Xna.Framework.Graphics; +using FlatRedBall; +using Microsoft.Xna.Framework; +using FlatRedBall.Content; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using FlatRedBall.Input; +using FlatRedBall.Debugging; +using FlatRedBall.Math; +using TMXGlueLib.DataTypes; + +namespace FlatRedBall.TileGraphics +{ + public enum SortAxis + { + None, + X, + Y + } + + public class MapDrawableBatch : PositionedObject, IVisible, IDrawableBatch + { + #region Fields + protected Tileset mTileset; + + #region XML Docs + /// + /// The effect used to draw. Shared by all instances for performance reasons + /// + #endregion + private static BasicEffect mBasicEffect; + private static AlphaTestEffect mAlphaTestEffect; + + /// + /// The vertices used to draw the map. + /// + /// + /// Coordinate order is: + /// 3 2 + /// + /// 0 1 + /// + protected VertexPositionTexture[] mVertices; + protected Texture2D mTexture; + #region XML Docs + /// + /// The indices to draw the shape + /// + #endregion + protected int[] mIndices; + + Dictionary> mNamedTileOrderedIndexes = new Dictionary>(); + + private int mCurrentNumberOfTiles = 0; + + + private SortAxis mSortAxis; + + #endregion + + #region Properties + + public List Properties + { + get; + private set; + } = new List(); + + /// + /// The axis on which tiles are sorted. This is used to perform tile culling for performance. + /// Setting this to SortAxis.None will turn off culling. + /// + public SortAxis SortAxis + { + get + { + return mSortAxis; + } + set + { + mSortAxis = value; + } + } + + #region XML Docs + /// + /// Here we tell the engine if we want this batch + /// updated every frame. Since we have no updating to + /// do though, we will set this to false + /// + #endregion + public bool UpdateEveryFrame + { + get { return true; } + } + + public float RenderingScale + { + get; + set; + } + + public Dictionary> NamedTileOrderedIndexes + { + get + { + return mNamedTileOrderedIndexes; + } + } + + public bool Visible + { + get; + set; + } + + public bool ZBuffered + { + get; + set; + } + + public int QuadCount + { + get + { + return mVertices.Length / 4; + } + } + + public VertexPositionTexture[] Vertices + { + get + { + return mVertices; + } + } + + public Texture2D Texture + { + get + { + return mTexture; + } + set + { + if (value == null) + { + throw new Exception("Texture can't be null."); + } + + if (mTexture != null && (mTexture.Width != value.Width || mTexture.Height != value.Height)) + { + throw new Exception("New texture must match previous texture dimensions."); + } + + mTexture = value; + } + } + + // Doing these properties this way lets me avoid a computational step of 1 - ParallaxMultiplier in the Update() function + // To explain the get & set values, algebra: + // if _parallaxMultiplier = 1 - value (set) + // then _parallaxMultiplier - 1 = -value + // so -(_parallaxMultiplier - 1) = value + // thus -_parallaxMultiplier + 1 = value (get) + private float _parallaxMultiplierX; + public float ParallaxMultiplierX + { + get { return -_parallaxMultiplierX + 1; } + set { _parallaxMultiplierX = 1 - value; } + } + + private float _parallaxMultiplierY; + public float ParallaxMultiplierY + { + get { return -_parallaxMultiplierY + 1; } + set { _parallaxMultiplierY = 1 - value; } + } + + public TextureFilter? TextureFilter { get; set; } = null; + + + #endregion + + #region Constructor / Initialization + + // this exists purely for Clone + public MapDrawableBatch() + { + + } + + public MapDrawableBatch(int numberOfTiles, Texture2D texture) + : base() + { + if (texture == null) + throw new ArgumentNullException("texture"); + + Visible = true; + InternalInitialize(); + + mTexture = texture; + mVertices = new VertexPositionTexture[4 * numberOfTiles]; + mIndices = new int[6 * numberOfTiles]; + } + + #region XML Docs + /// + /// Create and initialize all assets + /// + #endregion + public MapDrawableBatch(int numberOfTiles, int textureTileDimensionWidth, int textureTileDimensionHeight, Texture2D texture) + : base() + { + if (texture == null) + throw new ArgumentNullException("texture"); + + Visible = true; + InternalInitialize(); + + mTexture = texture; + mVertices = new VertexPositionTexture[4 * numberOfTiles]; + mIndices = new int[6 * numberOfTiles]; + + mTileset = new Tileset(texture, textureTileDimensionWidth, textureTileDimensionHeight); + } + + //public MapDrawableBatch(int mapWidth, int mapHeight, float mapTileDimension, int textureTileDimension, string tileSetFilename) + // : base() + //{ + // InternalInitialize(); + + + // mTileset = new Tileset(tileSetFilename, textureTileDimension); + // mMapWidth = mapWidth; + // mMapHeight = mapHeight; + + // int numberOfTiles = mapWidth * mapHeight; + // // the number of vertices is 4 times the number of tiles (each tile gets 4 vertices) + // mVertices = new VertexPositionTexture[4 * numberOfTiles]; + + // // the number of indices is 6 times the number of tiles + // mIndices = new short[6 * numberOfTiles]; + // for(int i = 0; i < mapHeight; i++) + // { + // for (int j = 0; j < mapWidth; j++) + // { + // int currentTile = mapHeight * i + j; + // int currentVertex = currentTile * 4; + // float xOffset = j * mapTileDimension; + // float yOffset = i * mapTileDimension; + // int currentIndex = currentTile * 6; // 6 indices per tile + + // // TEMP + // Vector2[] coords = mTileset.GetTextureCoordinateVectorsOfTextureIndex(new Random().Next()%4); + // // END TEMP + + + // // create vertices + // mVertices[currentVertex + 0] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + 0f, 0f), coords[0]); + // mVertices[currentVertex + 1] = new VertexPositionTexture(new Vector3(xOffset + mapTileDimension, yOffset + 0f, 0f), coords[1]); + // mVertices[currentVertex + 2] = new VertexPositionTexture(new Vector3(xOffset + mapTileDimension, yOffset + mapTileDimension, 0f), coords[2]); + // mVertices[currentVertex + 3] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + mapTileDimension, 0f), coords[3]); + + + // // create indices + // mIndices[currentIndex + 0] = (short)(currentVertex + 0); + // mIndices[currentIndex + 1] = (short)(currentVertex + 1); + // mIndices[currentIndex + 2] = (short)(currentVertex + 2); + // mIndices[currentIndex + 3] = (short)(currentVertex + 0); + // mIndices[currentIndex + 4] = (short)(currentVertex + 2); + // mIndices[currentIndex + 5] = (short)(currentVertex + 3); + + // mCurrentNumberOfTiles++; + // } + // } + // mTexture = FlatRedBallServices.Load(@"content/tiles"); + + + + //} + + void InternalInitialize() + { + // We're going to share these because creating effects is slow... + // But is this okay if we tombstone? + if (mBasicEffect == null) + { + mBasicEffect = new BasicEffect(FlatRedBallServices.GraphicsDevice); + + mBasicEffect.VertexColorEnabled = false; + mBasicEffect.TextureEnabled = true; + } + if (mAlphaTestEffect == null) + { + mAlphaTestEffect = new AlphaTestEffect(FlatRedBallServices.GraphicsDevice); + mAlphaTestEffect.Alpha = 1; + mAlphaTestEffect.VertexColorEnabled = false; + + } + + RenderingScale = 1; + + + } + + #endregion + + #region Methods + + public void AddToManagers() + { + SpriteManager.AddDrawableBatch(this); + //SpriteManager.AddPositionedObject(mMapBatch); + } + + public void AddToManagers(Layer layer) + { + SpriteManager.AddToLayer(this, layer); + } + + public static MapDrawableBatch FromScnx(string sceneFileName, string contentManagerName, bool verifySameTexturePerLayer) + { + // TODO: This line crashes when the path is already absolute! + string absoluteFileName = FileManager.MakeAbsolute(sceneFileName); + + // TODO: The exception doesn't make sense when the file type is wrong. + SceneSave saveInstance = SceneSave.FromFile(absoluteFileName); + + int startingIndex = 0; + + string oldRelativeDirectory = FileManager.RelativeDirectory; + FileManager.RelativeDirectory = FileManager.GetDirectory(absoluteFileName); + + // get the list of sprites from our map file + List spriteSaveList = saveInstance.SpriteList; + + // we use the sprites as defined in the scnx file to create and draw the map. + MapDrawableBatch mMapBatch = FromSpriteSaves(spriteSaveList, startingIndex, spriteSaveList.Count, contentManagerName, verifySameTexturePerLayer); + + FileManager.RelativeDirectory = oldRelativeDirectory; + // temp + //mMapBatch = new MapDrawableBatch(32, 32, 32f, 64, @"content/tiles"); + return mMapBatch; + + } + + /* This creates a MapDrawableBatch (MDB) from the list of sprites provided to us by the FlatRedBall (FRB) Scene XML (scnx) file. */ + public static MapDrawableBatch FromSpriteSaves(List spriteSaveList, int startingIndex, int count, string contentManagerName, bool verifySameTexturesPerLayer) + { + +#if DEBUG + if (verifySameTexturesPerLayer) + { + VerifySingleTexture(spriteSaveList, startingIndex, count); + } +#endif + + // We got it! We are going to make some assumptions: + // First we need the texture. We'll assume all Sprites + // use the same texture: + + // TODO: I (Bryan) really HATE this assumption. But it will work for now. + SpriteSave firstSprite = spriteSaveList[startingIndex]; + + // This is the file name of the texture, but the file name is relative to the .scnx location + string textureRelativeToScene = firstSprite.Texture; + // so we load the texture + Texture2D texture = FlatRedBallServices.Load(textureRelativeToScene, contentManagerName); + + if (!MathFunctions.IsPowerOfTwo(texture.Width) || !MathFunctions.IsPowerOfTwo(texture.Height)) + { + throw new Exception("The dimensions of the texture file " + texture.Name + " are not power of 2!"); + } + + + // Assume all the dimensions of the textures are the same. I.e. all tiles use the same texture width and height. + // This assumption is safe for Iso and Ortho tile maps. + int tileFileDimensionsWidth = 0; + int tileFileDimensionsHeight = 0; + if (spriteSaveList.Count > startingIndex) + { + SpriteSave s = spriteSaveList[startingIndex]; + + // deduce the dimensionality of the tile from the texture coordinates + tileFileDimensionsWidth = (int)System.Math.Round((double)((s.RightTextureCoordinate - s.LeftTextureCoordinate) * texture.Width)); + tileFileDimensionsHeight = (int)System.Math.Round((double)((s.BottomTextureCoordinate - s.TopTextureCoordinate) * texture.Height)); + + } + + + // alas, we create the MDB + MapDrawableBatch mMapBatch = new MapDrawableBatch(count, tileFileDimensionsWidth, tileFileDimensionsHeight, texture); + + int lastIndexExclusive = startingIndex + count; + + for (int i = startingIndex; i < lastIndexExclusive; i++) + { + SpriteSave spriteSave = spriteSaveList[i]; + + // We don't want objects within the IDB to have a different Z than the IDB itself + // (if possible) because that makes the IDB behave differently when using sorting vs. + // the zbuffer. + const bool setZTo0 = true; + mMapBatch.Paste(spriteSave, setZTo0); + + } + + + + return mMapBatch; + } + + public MapDrawableBatch Clone() + { + return base.Clone(); + } + + // Bring the texture coordinates in to adjust for rendering issues on dx9/ogl + public const float CoordinateAdjustment = .00002f; + + internal static MapDrawableBatch FromReducedLayer(TMXGlueLib.DataTypes.ReducedLayerInfo reducedLayerInfo, LayeredTileMap owner, TMXGlueLib.DataTypes.ReducedTileMapInfo rtmi, string contentManagerName) + { + int tileDimensionWidth = reducedLayerInfo.TileWidth; + int tileDimensionHeight = reducedLayerInfo.TileHeight; + float quadWidth = reducedLayerInfo.TileWidth; + float quadHeight = reducedLayerInfo.TileHeight; + + string textureName = reducedLayerInfo.Texture; + + +#if IOS || ANDROID + + textureName = textureName.ToLowerInvariant(); + +#endif + + Texture2D texture = FlatRedBallServices.Load(textureName, contentManagerName); + + MapDrawableBatch toReturn = new MapDrawableBatch(reducedLayerInfo.Quads.Count, tileDimensionWidth, tileDimensionHeight, texture); + + toReturn.Name = reducedLayerInfo.Name; + + Vector3 position = new Vector3(); + Vector2 tileDimensions = new Vector2(quadWidth, quadHeight); + + + IEnumerable quads = null; + + if (rtmi.NumberCellsWide > rtmi.NumberCellsTall) + { + quads = reducedLayerInfo.Quads.OrderBy(item => item.LeftQuadCoordinate).ToList(); + toReturn.mSortAxis = SortAxis.X; + } + else + { + quads = reducedLayerInfo.Quads.OrderBy(item => item.BottomQuadCoordinate).ToList(); + toReturn.mSortAxis = SortAxis.Y; + } + + foreach (var quad in quads) + { + position.X = quad.LeftQuadCoordinate; + position.Y = quad.BottomQuadCoordinate; + + // The Z of the quad should be relative to this layer, not absolute Z values. + // A multi-layer map will offset the individual layer Z values, the quads should have a Z of 0. + // position.Z = reducedLayerInfo.Z; + + + var textureValues = new Vector4(); + + // The purpose of CoordinateAdjustment is to bring the texture values "in", to reduce the chance of adjacent + // tiles drawing on a given tile quad. If we don't do this, we can get slivers of adjacent colors appearing, causing + // lines or grid patterns. + // To bring the values "in" we have to consider rotated quads. + textureValues.X = CoordinateAdjustment + (float)quad.LeftTexturePixel / (float)texture.Width; // Left + textureValues.Y = -CoordinateAdjustment + (float)(quad.LeftTexturePixel + tileDimensionWidth) / (float)texture.Width; // Right + textureValues.Z = CoordinateAdjustment + (float)quad.TopTexturePixel / (float)texture.Height; // Top + textureValues.W = -CoordinateAdjustment + (float)(quad.TopTexturePixel + tileDimensionHeight) / (float)texture.Height; // Bottom + + // pad before doing any rotations/flipping + const bool pad = true; + if (pad) + { + const float amountToAdd = .0000001f; + textureValues.X += amountToAdd; // Left + textureValues.Y -= amountToAdd; // Right + textureValues.Z += amountToAdd; // Top + textureValues.W -= amountToAdd; // Bottom + } + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedHorizontallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedHorizontallyFlag) + { + var temp = textureValues.Y; + textureValues.Y = textureValues.X; + textureValues.X = temp; + } + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedVerticallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedVerticallyFlag) + { + var temp = textureValues.Z; + textureValues.Z = textureValues.W; + textureValues.W = temp; + } + + int tileIndex = toReturn.AddTile(position, tileDimensions, + //quad.LeftTexturePixel, quad.TopTexturePixel, quad.LeftTexturePixel + tileDimensionWidth, quad.TopTexturePixel + tileDimensionHeight); + textureValues); + + if ((quad.FlipFlags & TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedDiagonallyFlag) == TMXGlueLib.DataTypes.ReducedQuadInfo.FlippedDiagonallyFlag) + { + toReturn.ApplyDiagonalFlip(tileIndex); + } + + // This was moved to outside of this conversion, to support shaps + //if (quad.QuadSpecificProperties != null) + //{ + // var listToAdd = quad.QuadSpecificProperties.ToList(); + // listToAdd.Add(new NamedValue { Name = "Name", Value = quad.Name }); + // owner.Properties.Add(quad.Name, listToAdd); + //} + if (quad.RotationDegrees != 0) + { + // Tiled rotates clockwise :( + var rotationRadians = -MathHelper.ToRadians(quad.RotationDegrees); + + Vector3 bottomLeftPos = toReturn.Vertices[tileIndex * 4].Position; + + Vector3 vertPos = toReturn.Vertices[tileIndex * 4 + 1].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 1].Position = vertPos; + + vertPos = toReturn.Vertices[tileIndex * 4 + 2].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 2].Position = vertPos; + + vertPos = toReturn.Vertices[tileIndex * 4 + 3].Position; + MathFunctions.RotatePointAroundPoint(bottomLeftPos, ref vertPos, rotationRadians); + toReturn.Vertices[tileIndex * 4 + 3].Position = vertPos; + + } + + toReturn.RegisterName(quad.Name, tileIndex); + } + + return toReturn; + } + + public void Paste(Sprite sprite) + { + Paste(sprite, false); + } + + public int Paste(Sprite sprite, bool setZTo0) + { + // here we have the Sprite's X and Y in absolute coords as well as its texture coords + // NOTE: I appended the Z coordinate for the sake of iso maps. This SHOULDN'T have an effect on the ortho maps since I believe the + // TMX->SCNX tool sets all z to zero. + + // The AddTile method expects the bottom-left corner + float x = sprite.X - sprite.ScaleX; + float y = sprite.Y - sprite.ScaleY; + float z = sprite.Z; + + if (setZTo0) + { + z = 0; + } + + float width = 2f * sprite.ScaleX; // w + float height = 2f * sprite.ScaleY; // z + + float topTextureCoordinate = sprite.TopTextureCoordinate; + float bottomTextureCoordinate = sprite.BottomTextureCoordinate; + float leftTextureCoordinate = sprite.LeftTextureCoordinate; + float rightTextureCoordinate = sprite.RightTextureCoordinate; + + int tileIndex = mCurrentNumberOfTiles; + + RegisterName(sprite.Name, tileIndex); + + // add the textured tile to our map so that we may draw it. + return AddTile(new Vector3(x, y, z), + new Vector2(width, height), + new Vector4(leftTextureCoordinate, rightTextureCoordinate, topTextureCoordinate, bottomTextureCoordinate)); + + } + + public void Paste(SpriteSave spriteSave) + { + Paste(spriteSave, false); + } + + public int Paste(SpriteSave spriteSave, bool setZTo0) + { + // here we have the Sprite's X and Y in absolute coords as well as its texture coords + // NOTE: I appended the Z coordinate for the sake of iso maps. This SHOULDN'T have an effect on the ortho maps since I believe the + // TMX->SCNX tool sets all z to zero. + + // The AddTile method expects the bottom-left corner + float x = spriteSave.X - spriteSave.ScaleX; + float y = spriteSave.Y - spriteSave.ScaleY; + float z = spriteSave.Z; + if (setZTo0) + { + z = 0; + } + + float width = 2f * spriteSave.ScaleX; // w + float height = 2f * spriteSave.ScaleY; // z + + float topTextureCoordinate = spriteSave.TopTextureCoordinate; + float bottomTextureCoordinate = spriteSave.BottomTextureCoordinate; + float leftTextureCoordinate = spriteSave.LeftTextureCoordinate; + float rightTextureCoordinate = spriteSave.RightTextureCoordinate; + + int tileIndex = mCurrentNumberOfTiles; + + RegisterName(spriteSave.Name, tileIndex); + + // add the textured tile to our map so that we may draw it. + return AddTile(new Vector3(x, y, z), new Vector2(width, height), new Vector4(leftTextureCoordinate, rightTextureCoordinate, topTextureCoordinate, bottomTextureCoordinate)); + } + + private static void VerifySingleTexture(List spriteSaveList, int startingIndex, int count) + { + // Every Sprite should either have the same texture + if (spriteSaveList.Count != 0) + { + string texture = spriteSaveList[startingIndex].Texture; + + for (int i = startingIndex + 1; i < startingIndex + count; i++) + { + SpriteSave ss = spriteSaveList[i]; + + if (ss.Texture != texture) + { + float leftOfSprite = ss.X - ss.ScaleX; + float indexX = leftOfSprite / (ss.ScaleX * 2); + + float topOfSprite = ss.Y + ss.ScaleY; + float indexY = (0 - topOfSprite) / (ss.ScaleY * 2); + + throw new Exception("All Sprites do not have the same texture"); + } + } + + } + } + + private void RegisterName(string name, int tileIndex) + { + int throwaway; + if (!string.IsNullOrEmpty(name) && !int.TryParse(name, out throwaway)) + { + // TEMPORARY: + // The tmx converter + // names all Sprites with + // a number if their name is + // not explicitly set. Therefore + // we have to ignore those and look + // for explicit names (names not numbers). + // Will talk to Domenic about this to fix it. + if (!mNamedTileOrderedIndexes.ContainsKey(name)) + { + mNamedTileOrderedIndexes.Add(name, new List()); + } + + mNamedTileOrderedIndexes[name].Add(tileIndex); + } + } + + Vector2[] coords = new Vector2[4]; + + /// + /// Paints a texture on a tile. This method takes the index of the Sprite in the order it was added + /// to the MapDrawableBatch, so it supports any configuration including non-rectangular maps and maps with + /// gaps. + /// + /// The index of the tile to paint - this matches the index of the tile as it was added. + /// + public void PaintTile(int orderedTileIndex, int newTextureId) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Reusing the coords array saves us on allocation + mTileset.GetTextureCoordinateVectorsOfTextureIndex(newTextureId, coords); + + // Coords are + // 3 2 + // + // 0 1 + + mVertices[currentVertex + 0].TextureCoordinate = coords[0]; + mVertices[currentVertex + 1].TextureCoordinate = coords[1]; + mVertices[currentVertex + 2].TextureCoordinate = coords[2]; + mVertices[currentVertex + 3].TextureCoordinate = coords[3]; + + } + + /// + /// Sets the left and top texture coordiantes of the tile represented by orderedTileIndex. The right and bottom texture coordaintes + /// are set automatically according to the tileset dimensions. + /// + /// The ordered tile index. + /// The left texture coordiante (in UV coordinates) + /// The top texture coordainte (in UV coordinates) + public void PaintTileTextureCoordinates(int orderedTileIndex, float textureXCoordinate, float textureYCoordinate) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + mTileset.GetCoordinatesForTile(coords, textureXCoordinate, textureYCoordinate); + + mVertices[currentVertex + 0].TextureCoordinate = coords[0]; + mVertices[currentVertex + 1].TextureCoordinate = coords[1]; + mVertices[currentVertex + 2].TextureCoordinate = coords[2]; + mVertices[currentVertex + 3].TextureCoordinate = coords[3]; + } + + public void PaintTileTextureCoordinates(int orderedTileIndex, float leftCoordinate, float topCoordinate, float rightCoordinate, float bottomCoordinate) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + mVertices[currentVertex + 0].TextureCoordinate.X = leftCoordinate; + mVertices[currentVertex + 0].TextureCoordinate.Y = bottomCoordinate; + + mVertices[currentVertex + 1].TextureCoordinate.X = rightCoordinate; + mVertices[currentVertex + 1].TextureCoordinate.Y = bottomCoordinate; + + mVertices[currentVertex + 2].TextureCoordinate.X = rightCoordinate; + mVertices[currentVertex + 2].TextureCoordinate.Y = topCoordinate; + + mVertices[currentVertex + 3].TextureCoordinate.X = leftCoordinate; + mVertices[currentVertex + 3].TextureCoordinate.Y = topCoordinate; + } + + + + + // Swaps the top-right for the bottom-left verts + public void ApplyDiagonalFlip(int orderedTileIndex) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + var old0 = mVertices[currentVertex + 0].TextureCoordinate; + + mVertices[currentVertex + 0].TextureCoordinate = mVertices[currentVertex + 2].TextureCoordinate; + mVertices[currentVertex + 2].TextureCoordinate = old0; + } + + public void RotateTextureCoordinatesCounterclockwise(int orderedTileIndex) + { + int currentVertex = orderedTileIndex * 4; // 4 vertices per tile + + // Coords are + // 3 2 + // + // 0 1 + + var old3 = mVertices[currentVertex + 3].TextureCoordinate; + + mVertices[currentVertex + 3].TextureCoordinate = mVertices[currentVertex + 2].TextureCoordinate; + mVertices[currentVertex + 2].TextureCoordinate = mVertices[currentVertex + 1].TextureCoordinate; + mVertices[currentVertex + 1].TextureCoordinate = mVertices[currentVertex + 0].TextureCoordinate; + mVertices[currentVertex + 0].TextureCoordinate = old3; + + } + + public void GetTextureCoordiantesForOrderedTile(int orderedTileIndex, out float textureX, out float textureY) + { + // The order is: + // 3 2 + // + // 0 1 + + // So we want to add 3 to the index to get the top-left vert, then use + // the texture coordinates there to get the + Vector2 vector = mVertices[(orderedTileIndex * 4) + 3].TextureCoordinate; + + textureX = vector.X; + textureY = vector.Y; + } + + public void GetBottomLeftWorldCoordinateForOrderedTile(int orderedTileIndex, out float x, out float y) + { + // The order is: + // 3 2 + // + // 0 1 + + // So we just need to mutiply by 4 and not add anything + Vector3 vector = mVertices[(orderedTileIndex * 4)].Position; + + x = vector.X; + y = vector.Y; + } + + /// + /// Adds a tile to the tile map + /// + /// + /// + /// + /// 4 points defining the boundaries in the texture for the tile. + /// (X = left, Y = right, Z = top, W = bottom) + /// + public int AddTile(Vector3 bottomLeftPosition, Vector2 dimensions, Vector4 texture) + { + int toReturn = mCurrentNumberOfTiles; + int currentVertex = mCurrentNumberOfTiles * 4; + + int currentIndex = mCurrentNumberOfTiles * 6; // 6 indices per tile (there are mVertices.Length/4 tiles) + + float xOffset = bottomLeftPosition.X; + float yOffset = bottomLeftPosition.Y; + float zOffset = bottomLeftPosition.Z; + + float width = dimensions.X; + float height = dimensions.Y; + + + // create vertices + mVertices[currentVertex + 0] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + 0f, zOffset), new Vector2(texture.X, texture.W)); + mVertices[currentVertex + 1] = new VertexPositionTexture(new Vector3(xOffset + width, yOffset + 0f, zOffset), new Vector2(texture.Y, texture.W)); + mVertices[currentVertex + 2] = new VertexPositionTexture(new Vector3(xOffset + width, yOffset + height, zOffset), new Vector2(texture.Y, texture.Z)); + mVertices[currentVertex + 3] = new VertexPositionTexture(new Vector3(xOffset + 0f, yOffset + height, zOffset), new Vector2(texture.X, texture.Z)); + + // create indices + mIndices[currentIndex + 0] = currentVertex + 0; + mIndices[currentIndex + 1] = currentVertex + 1; + mIndices[currentIndex + 2] = currentVertex + 2; + mIndices[currentIndex + 3] = currentVertex + 0; + mIndices[currentIndex + 4] = currentVertex + 2; + mIndices[currentIndex + 5] = currentVertex + 3; + + mCurrentNumberOfTiles++; + + return toReturn; + } + + /// + /// Add a tile to the map + /// + /// + /// + /// Top left X coordinate in the core texture + /// Top left Y coordinate in the core texture + /// Bottom right X coordinate in the core texture + /// Bottom right Y coordinate in the core texture + public int AddTile(Vector3 bottomLeftPosition, Vector2 tileDimensions, int textureTopLeftX, int textureTopLeftY, int textureBottomRightX, int textureBottomRightY) + { + // Form vector4 for AddTile overload + var textureValues = new Vector4(); + textureValues.X = (float)textureTopLeftX / (float)mTexture.Width; // Left + textureValues.Y = (float)textureBottomRightX / (float)mTexture.Width; // Right + textureValues.Z = (float)textureTopLeftY / (float)mTexture.Height; // Top + textureValues.W = (float)textureBottomRightY / (float)mTexture.Height; // Bottom + + return AddTile(bottomLeftPosition, tileDimensions, textureValues); + } + + /// + /// Renders the MapDrawableBatch + /// + /// The currently drawing camera + public void Draw(Camera camera) + { + ////////////////////Early Out/////////////////// + + if (!AbsoluteVisible) + { + return; + } + if (mVertices.Length == 0) + { + return; + } + + //////////////////End Early Out///////////////// + + + + int firstVertIndex; + int lastVertIndex; + int indexStart; + int numberOfTriangles; + GetRenderingIndexValues(camera, out firstVertIndex, out lastVertIndex, out indexStart, out numberOfTriangles); + + if (numberOfTriangles != 0) + { + TextureFilter? oldTextureFilter = null; + + if (this.TextureFilter != null && this.TextureFilter != FlatRedBallServices.GraphicsOptions.TextureFilter) + { + oldTextureFilter = FlatRedBallServices.GraphicsOptions.TextureFilter; + FlatRedBallServices.GraphicsOptions.TextureFilter = this.TextureFilter.Value; + } + TextureAddressMode oldTextureAddressMode; + Effect effectTouse = PrepareRenderingStates(camera, out oldTextureAddressMode); + + foreach (EffectPass pass in effectTouse.CurrentTechnique.Passes) + { + // Start each pass + + pass.Apply(); + + int numberVertsToDraw = lastVertIndex - firstVertIndex; + + // Right now this uses the (slower) DrawUserIndexedPrimitives + // It could use DrawIndexedPrimitives instead for much faster performance, + // but to do that we'd have to keep VB's around and make sure to re-create them + // whenever the graphics device is lost. + FlatRedBallServices.GraphicsDevice.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + mVertices, + firstVertIndex, + numberVertsToDraw, + mIndices, + indexStart, numberOfTriangles); + + } + + Renderer.TextureAddressMode = oldTextureAddressMode; + if (ZBuffered) + { + FlatRedBallServices.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + } + if (oldTextureFilter != null) + { + FlatRedBallServices.GraphicsOptions.TextureFilter = oldTextureFilter.Value; + } + } + } + + private Effect PrepareRenderingStates(Camera camera, out TextureAddressMode oldTextureAddressMode) + { + // Set graphics states + FlatRedBallServices.GraphicsDevice.RasterizerState = RasterizerState.CullNone; + FlatRedBall.Graphics.Renderer.BlendOperation = BlendOperation.Regular; + + Effect effectTouse = null; + + if (ZBuffered) + { + FlatRedBallServices.GraphicsDevice.DepthStencilState = DepthStencilState.Default; + camera.SetDeviceViewAndProjection(mAlphaTestEffect, false); + + mAlphaTestEffect.World = Matrix.CreateScale(RenderingScale) * base.TransformationMatrix; + mAlphaTestEffect.Texture = mTexture; + + effectTouse = mAlphaTestEffect; + } + else + { + camera.SetDeviceViewAndProjection(mBasicEffect, false); + + mBasicEffect.World = Matrix.CreateScale(RenderingScale) * base.TransformationMatrix; + mBasicEffect.Texture = mTexture; + effectTouse = mBasicEffect; + } + + + + // We won't need to use any other kind of texture + // address mode besides clamp, and clamp is required + // on the "Reach" profile when the texture is not power + // of two. Let's set it to clamp here so that we don't crash + // on non-power-of-two textures. + oldTextureAddressMode = Renderer.TextureAddressMode; + Renderer.TextureAddressMode = TextureAddressMode.Clamp; + + + + + return effectTouse; + } + + private void GetRenderingIndexValues(Camera camera, out int firstVertIndex, out int lastVertIndex, out int indexStart, out int numberOfTriangles) + { + + firstVertIndex = 0; + + lastVertIndex = mVertices.Length; + + + float tileWidth = mVertices[1].Position.X - mVertices[0].Position.X; + + if (mSortAxis == SortAxis.X) + { + float minX = camera.AbsoluteLeftXEdgeAt(this.Z); + float maxX = camera.AbsoluteRightXEdgeAt(this.Z); + + minX -= this.X; + maxX -= this.X; + + firstVertIndex = GetFirstAfterX(mVertices, minX - tileWidth); + lastVertIndex = GetFirstAfterX(mVertices, maxX) + 4; + } + else if (mSortAxis == SortAxis.Y) + { + float minY = camera.AbsoluteBottomYEdgeAt(this.Z); + float maxY = camera.AbsoluteTopYEdgeAt(this.Z); + + minY -= this.Y; + maxY -= this.Y; + + firstVertIndex = GetFirstAfterY(mVertices, minY - tileWidth); + lastVertIndex = GetFirstAfterY(mVertices, maxY) + 4; + } + + lastVertIndex = System.Math.Min(lastVertIndex, mVertices.Length); + + indexStart = 0;// (firstVertIndex * 3) / 2; + int indexEndExclusive = ((lastVertIndex - firstVertIndex) * 3) / 2; + + numberOfTriangles = (indexEndExclusive - indexStart) / 3; + } + + public static int GetFirstAfterX(VertexPositionTexture[] list, float xGreaterThan) + { + int min = 0; + int originalMax = list.Length / 4; + int max = list.Length / 4; + + int mid = (max + min) / 2; + + while (min < max) + { + mid = (max + min) / 2; + float midItem = list[mid * 4].Position.X; + + if (midItem > xGreaterThan) + { + // Is this the last one? + // Not sure why this is here, because if we have just 2 items, + // this will always return a value of 1 instead + //if (mid * 4 + 4 >= list.Length) + //{ + // return mid * 4; + //} + + // did we find it? + if (mid > 0 && list[(mid - 1) * 4].Position.X <= xGreaterThan) + { + return mid * 4; + } + else + { + max = mid - 1; + } + } + else if (midItem <= xGreaterThan) + { + if (mid == 0) + { + return mid * 4; + } + else if (mid < originalMax - 1 && list[(mid + 1) * 4].Position.X > xGreaterThan) + { + return (mid + 1) * 4; + } + else + { + min = mid + 1; + } + } + } + if (min == 0) + { + return 0; + } + else + { + return list.Length; + } + } + + public static int GetFirstAfterY(VertexPositionTexture[] list, float yGreaterThan) + { + int min = 0; + int originalMax = list.Length / 4; + int max = list.Length / 4; + + int mid = (max + min) / 2; + + while (min < max) + { + mid = (max + min) / 2; + float midItem = list[mid * 4].Position.Y; + + if (midItem > yGreaterThan) + { + // Is this the last one? + // See comment in GetFirstAfterX + //if (mid * 4 + 4 >= list.Length) + //{ + // return mid * 4; + //} + + // did we find it? + if (mid > 0 && list[(mid - 1) * 4].Position.Y <= yGreaterThan) + { + return mid * 4; + } + else + { + max = mid - 1; + } + } + else if (midItem <= yGreaterThan) + { + if (mid == 0) + { + return mid * 4; + } + else if (mid < originalMax - 1 && list[(mid + 1) * 4].Position.Y > yGreaterThan) + { + return (mid + 1) * 4; + } + else + { + min = mid + 1; + } + } + } + if (min == 0) + { + return 0; + } + else + { + return list.Length; + } + } + #region XML Docs + /// + /// Here we update our batch - but this batch doesn't + /// need to be updated + /// + #endregion + public void Update() + { + float leftView = Camera.Main.AbsoluteLeftXEdgeAt(0); + float topView = Camera.Main.AbsoluteTopYEdgeAt(0); + + float cameraOffsetX = leftView - CameraOriginX; + float cameraOffsetY = topView - CameraOriginY; + + this.RelativeX = cameraOffsetX * _parallaxMultiplierX; + this.RelativeY = cameraOffsetY * _parallaxMultiplierY; + + this.TimedActivity(TimeManager.SecondDifference, TimeManager.SecondDifferenceSquaredDividedByTwo, TimeManager.LastSecondDifference); + + // The MapDrawableBatch may be attached to a LayeredTileMap (the container of all layers) + // If so, the player may move the LayeredTileMap and expect all contained layers to move along + // with it. To allow this, we need to have dependencies updated. We'll do this by simply updating + // dependencies here, although I don't know at this point if there's a better way - like if we should + // be adding this to the SpriteManager's PositionedObjectList. This is an improvement so we'll do it for + // now and revisit this in case there's a problem in the future. + this.UpdateDependencies(TimeManager.CurrentTime); + } + + // TODO: I would like to somehow make this a property on the LayeredTileMap, but right now it is easier to put them here + public float CameraOriginY { get; set; } + public float CameraOriginX { get; set; } + + IVisible IVisible.Parent + { + get + { + return this.Parent as IVisible; + } + } + + public bool AbsoluteVisible + { + get + { + if (this.Visible) + { + var parentAsIVisible = this.Parent as IVisible; + + if (parentAsIVisible == null || IgnoresParentVisibility) + { + return true; + } + else + { + // this is true, so return if the parent is visible: + return parentAsIVisible.AbsoluteVisible; + } + } + else + { + return false; + } + } + } + + public bool IgnoresParentVisibility + { + get; + set; + } + + #region XML Docs + /// + /// Don't call this, instead call SpriteManager.RemoveDrawableBatch + /// + #endregion + public void Destroy() + { + this.RemoveSelfFromListsBelongingTo(); + } + + + public void MergeOntoThis(IEnumerable mapDrawableBatches) + { + int quadsToAdd = 0; + int quadsOnThis = QuadCount; + foreach (var mdb in mapDrawableBatches) + { + quadsToAdd += mdb.QuadCount; + } + + + int totalNumberOfVerts = 4 * (this.QuadCount + quadsToAdd); + int totalNumberOfIndexes = 6 * (this.QuadCount + quadsToAdd); + + var oldVerts = mVertices; + var oldIndexes = mIndices; + + mVertices = new VertexPositionTexture[totalNumberOfVerts]; + mIndices = new int[totalNumberOfIndexes]; + + oldVerts.CopyTo(mVertices, 0); + oldIndexes.CopyTo(mIndices, 0); + + int currentQuadIndex = quadsOnThis; + + + int index = 0; + foreach (var mdb in mapDrawableBatches) + { + int startVert = currentQuadIndex * 4; + int startIndex = currentQuadIndex * 6; + int numberOfIndices = mdb.mIndices.Length; + int numberOfNewVertices = mdb.mVertices.Length; + + mdb.mVertices.CopyTo(mVertices, startVert); + mdb.mIndices.CopyTo(mIndices, startIndex); + + + for (int i = startIndex; i < startIndex + numberOfIndices; i++) + { + mIndices[i] += startVert; + } + + for (int i = startVert; i < startVert + numberOfNewVertices; i++) + { + mVertices[i].Position.Z += index + 1; + } + + foreach (var kvp in mdb.mNamedTileOrderedIndexes) + { + string key = kvp.Key; + + List toAddTo; + + if (mNamedTileOrderedIndexes.ContainsKey(key)) + { + toAddTo = mNamedTileOrderedIndexes[key]; + } + else + { + toAddTo = new List(); + mNamedTileOrderedIndexes[key] = toAddTo; + } + + foreach (var namedIndex in kvp.Value) + { + toAddTo.Add(namedIndex + currentQuadIndex); + } + } + + + currentQuadIndex += mdb.QuadCount; + index++; + } + } + + + public void RemoveQuads(IEnumerable quadIndexes) + { + var vertList = mVertices.ToList(); + // Reverse - go from biggest to smallest + foreach (var indexToRemove in quadIndexes.Distinct().OrderBy(item => -item)) + { + // and go from biggest to smallest here too + vertList.RemoveAt(indexToRemove * 4 + 3); + vertList.RemoveAt(indexToRemove * 4 + 2); + vertList.RemoveAt(indexToRemove * 4 + 1); + vertList.RemoveAt(indexToRemove * 4 + 0); + } + + mVertices = vertList.ToArray(); + + // The mNamedTileOrderedIndexes is a dictionary that stores which indexes are stored + // with which tiles. For example, the key in the dictionary may be "Lava", in which case + // the value is the indexes of the tiles that use the Lava tile. + // If we do end up removing any quads, then all following quads will shift, so we need to + // adjust the indexes so the naming works correctly + + List orderedInts = quadIndexes.OrderBy(item => item).Distinct().ToList(); + int numberOfRemovals = 0; + foreach (var kvp in mNamedTileOrderedIndexes) + { + var ints = kvp.Value; + + numberOfRemovals = 0; + + for (int i = 0; i < ints.Count; i++) + { + // Nothing left to test, so subtract and move on.... + if (numberOfRemovals == orderedInts.Count) + { + ints[i] -= numberOfRemovals; + } + else if (ints[i] == orderedInts[numberOfRemovals]) + { + ints.Clear(); + break; + } + else if (ints[i] < orderedInts[numberOfRemovals]) + { + ints[i] -= numberOfRemovals; + } + else + { + while (numberOfRemovals < orderedInts.Count && ints[i] > orderedInts[numberOfRemovals]) + { + numberOfRemovals++; + } + if (numberOfRemovals < orderedInts.Count && ints[i] == orderedInts[numberOfRemovals]) + { + ints.Clear(); + break; + } + + ints[i] -= numberOfRemovals; + } + } + } + } + + #endregion + } + + + + + public static class MapDrawableBatchExtensionMethods + { + + + } + + +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapLayer.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapLayer.cs new file mode 100644 index 000000000..ec9d322a2 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapLayer.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ +#if !UWP + [Serializable] +#endif + public partial class MapLayer : AbstractMapLayer + { + #region Fields + + private IDictionary propertyDictionaryField = null; + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + + private mapLayerData[] dataField; + + private string nameField; + + private int widthField; + + private int heightField; + #endregion + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", this.Name); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("data", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = true)] + public mapLayerData[] data + { + get + { + return this.dataField; + } + set + { + this.dataField = value; + if (dataField != null) + { + foreach (mapLayerData layerData in dataField) + { + layerData.length = width * height; + } + } + } + } + + /// + [XmlAttribute("width")] + public int width + { + get + { + return this.widthField; + } + set + { + this.widthField = value; + if (this.data != null) + { + foreach (mapLayerData layerData in data) + { + layerData.length = width * height; + } + } + } + } + + /// + [XmlAttribute("height")] + public int height + { + get + { + return this.heightField; + } + set + { + this.heightField = value; + if (this.data != null) + { + foreach (mapLayerData layerData in data) + { + layerData.length = width * height; + } + } + } + } + + private int? visibleField; + [XmlAttribute("visible")] + public int visible + { + get + { + return this.visibleField.HasValue ? this.visibleField.Value : 1; + } + set + { + this.visibleField = value; + } + } + + [XmlAttribute("opacity")] + public float Opacity + { + get; set; + } + + [XmlIgnore] + public bool IsVisible + { + get + { + return visible != 0; + } + } + + [XmlIgnore] + public TiledMapSave.LayerVisibleBehavior VisibleBehavior = TiledMapSave.LayerVisibleBehavior.Ignore; + + public override string ToString() + { + return Name; + } + + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTileset.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTileset.cs new file mode 100644 index 000000000..2852cdb5d --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTileset.cs @@ -0,0 +1,348 @@ +using System.Collections.Generic; +using System.Xml.Serialization; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using FlatRedBall.IO; +using System.IO; + +namespace TMXGlueLib +{ + /// + [XmlType(AnonymousType = true)] +// ReSharper disable InconsistentNaming + [XmlRoot("mapTileset")] + public class Tileset +// ReSharper restore InconsistentNaming + { + private TilesetImage[] _imageField; + + private mapTilesetTileOffset[] _tileOffsetField; + + private string _sourceField; + + [XmlIgnore] + public string SourceDirectory + { + get + { + if (_sourceField != null && _sourceField.Contains("\\")) + { + // add 1 to include the ending directory + return _sourceField.Substring(0, _sourceField.LastIndexOf('\\') + 1); + } + else + { + return "."; + } + } + } + + public static bool ShouldLoadValuesFromSource = true; + + [XmlAttribute("source", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public string Source + { + get + { + return _sourceField; + } + set + { + this._sourceField = value; + + if (ShouldLoadValuesFromSource) + { + LoadValuesFromSource(); + } + } + } + + [XmlIgnore] + public bool IsShared + { + get + { + return !string.IsNullOrEmpty(Source); + } + } + + private void LoadValuesFromSource() + { + if (!string.IsNullOrEmpty(this._sourceField)) + { + _sourceField = _sourceField.Replace("/", "\\"); + + tileset xts = null; + + try + { + + xts = FileManager.XmlDeserialize(_sourceField); + } + catch (FileNotFoundException) + { + string fileAttemptedToLoad = _sourceField; + if (FileManager.IsRelative(_sourceField)) + { + fileAttemptedToLoad = FileManager.RelativeDirectory + _sourceField; + } + + string message = "Could not find the shared tsx file \n" + fileAttemptedToLoad + + "\nIf this is a relative file name, then the loader will use " + + "the FileManager's RelativeDirectory to make the file absolute. Therefore, be sure to set the FileManger's RelativeDirectory to the file represented by " + + "this fileset before setting this property if setting this property manually."; + + + throw new FileNotFoundException(message); + } + + if (xts.image != null) + { + + Images = new TilesetImage[xts.image.Length]; + + Parallel.For(0, xts.image.Length, count => + { + this.Images[count] = new TilesetImage + { + Source = xts.image[count].source, + height = xts.image[count].height != 0 ? xts.image[count].height : xts.tileheight, + width = xts.image[count].width != 0 ? xts.image[count].width : xts.tilewidth + }; + }); + } + this.Name = xts.name; + this.Margin = xts.margin; + this.Spacing = xts.spacing; + this.Tileheight = xts.tileheight; + this.Tilewidth = xts.tilewidth; + this.Tiles = xts.tile; + } + } + + /// + [XmlElement("tileoffset", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 1)] + public mapTilesetTileOffset[] Tileoffset + { + get + { + return this._tileOffsetField; + } + set + { + if (this._tileOffsetField == null || this._tileOffsetField.Length == 0) + { + this._tileOffsetField = value; + } + } + } + + + /// + [XmlElement("image", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 2)] + public TilesetImage[] Images + { + get + { + return this._imageField; + } + set + { + if (this._imageField == null || this._imageField.Length == 0) + { + this._imageField = value; + } + } + } + + + public bool ShouldSerializeImage() + { + return string.IsNullOrEmpty(this.Source); + } + + + [XmlArray("terraintypes", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 3)] + public List terraintypes = new List(); + + public bool ShouldSerializeterraintypes() + { + return string.IsNullOrEmpty(this.Source); + } + + [XmlElement("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 4)] + public List Tiles = new List(); + public bool ShouldSerializeTiles() + { + return string.IsNullOrEmpty(this.Source); + } + //{ + // get + // { + // return this.tileField; + // } + // set + // { + // if (this.tileField != null && this.tileField.Length > 0) + // { + // return; + // } + // else + // { + // this.tileField = value; + // } + // } + //} + + public void RefreshTileDictionary() + { + _tileDictionaryField = null; + } + + + private IDictionary _tileDictionaryField; + + [XmlIgnore] + public IDictionary TileDictionary + { + get + { + lock (this) + { + if (_tileDictionaryField == null) + { + _tileDictionaryField = new ConcurrentDictionary(); + + if (Tiles != null) + { + //Parallel.ForEach(tile, (t) => + // { + // if (t != null && !tileDictionaryField.ContainsKey((uint)t.id + 1)) + // { + // tileDictionaryField.Add((uint)t.id + 1, t); + // } + // }); + + foreach (var t in Tiles) + { + + // November 11, 2017 - why is this "+1"? + // That's confusing. Is it because in the old days + // it was hardcoded to be the ID of the tile including the offset? + // for multiple tilesets the offset won't be 1... + //uint key = (uint)t.id + 1; + uint key = (uint)t.id; + if (!_tileDictionaryField.ContainsKey(key)) + { + _tileDictionaryField.Add(key, t); + } + } + } + + return _tileDictionaryField; + + } + else + { + return _tileDictionaryField; + } + } + + } + } + + + + /// + [XmlAttribute("firstgid")] + public uint Firstgid + { + get; + set; + } + + /// + [XmlAttribute("name")] + public string Name + { + get; + set; + } + + public bool ShouldSerializeName() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("tilewidth")] + public int Tilewidth + { + get; + set; + } + + public bool ShouldSerializeTilewidth() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("tileheight")] + public int Tileheight + { + get; + set; + } + + public bool ShouldSerializeTileheight() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("spacing")] + public int Spacing + { + get; + set; + } + + public bool ShouldSerializeSpacing() + { + return string.IsNullOrEmpty(this.Source); + } + + /// + [XmlAttribute("margin")] + public int Margin + { + get; + set; + } + + public bool ShouldSerializeMargin() + { + return string.IsNullOrEmpty(this.Source); + } + + public override string ToString() + { + string toReturn = this.Name; + + if (!string.IsNullOrEmpty(Source)) + { + string sourceWithoutPath = FileManager.RemovePath(Source); + toReturn += " (" + sourceWithoutPath + ")"; + } + + return toReturn; + } + } + + public class mapTilesetTerrain + { + public string name { get; set; } + public int tile { get; set; } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTilesetTile.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTilesetTile.cs new file mode 100644 index 000000000..42ff1bfde --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/MapTilesetTile.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + + [XmlType(AnonymousType = true)] + public partial class mapTilesetTile + { + private IDictionary propertyDictionaryField = null; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + ForceRebuildPropertyDictionary(); + } + return propertyDictionaryField; + } + } + } + + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + /// + [XmlAttribute()] + public int id + { + get; + set; + } + + [XmlAttribute("type")] + public string Type { get; set; } + + [XmlElement("animation")] + public TileAnimation Animation + { + get; + set; + } + + [XmlElement("objectgroup")] + public mapObjectgroup Objects { get; set; } + + + + public mapTilesetTile() + { + } + + + public override string ToString() + { + string toReturn = id.ToString(); + + if(PropertyDictionary.Count != 0) + { + toReturn += " ("; + + foreach (var kvp in PropertyDictionary) + { + toReturn += "(" + kvp.Key + "," + kvp.Value + ")"; + } + + + toReturn += ")"; + } + return toReturn; + } + + public void ForceRebuildPropertyDictionary() + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/NamedValue.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/NamedValue.cs new file mode 100644 index 000000000..285280059 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/NamedValue.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TMXGlueLib.DataTypes +{ + public struct NamedValue + { + public string Name; + public string Type; + public object Value; + + public static NamedValue Empty = new NamedValue(); + + + public override string ToString() + { + if(string.IsNullOrEmpty(Type)) + { + return $"{Name}={Value}"; + } + else + { + return $"{Type} {Name}={Value}"; + } + + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs new file mode 100644 index 000000000..fb0ac926b --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.TiledMapSave.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlatRedBall.Content; +using FlatRedBall.Content.Scene; + +namespace TMXGlueLib.DataTypes +{ + public partial class ReducedQuadInfo + { + + + internal static ReducedQuadInfo FromSpriteSave(SpriteSave spriteSave, int textureWidth, int textureHeight) + { + ReducedQuadInfo toReturn = new ReducedQuadInfo(); + toReturn.LeftQuadCoordinate = spriteSave.X - spriteSave.ScaleX; + toReturn.BottomQuadCoordinate = spriteSave.Y - spriteSave.ScaleY; + + + bool isRotated = spriteSave.RotationZ != 0; + if (isRotated) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedDiagonallyFlag); + } + + var leftTextureCoordinate = System.Math.Min(spriteSave.LeftTextureCoordinate, spriteSave.RightTextureCoordinate); + var topTextureCoordinate = System.Math.Min(spriteSave.TopTextureCoordinate, spriteSave.BottomTextureCoordinate); + + if (spriteSave.LeftTextureCoordinate > spriteSave.RightTextureCoordinate) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedHorizontallyFlag); + } + + if (spriteSave.TopTextureCoordinate > spriteSave.BottomTextureCoordinate) + { + toReturn.FlipFlags = (byte)(toReturn.FlipFlags | ReducedQuadInfo.FlippedVerticallyFlag); + } + + toReturn.LeftTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(leftTextureCoordinate * textureWidth); + toReturn.TopTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(topTextureCoordinate * textureHeight); + + toReturn.Name = spriteSave.Name; + + return toReturn; + } + + + } + + public partial class ReducedTileMapInfo + { + public static bool FastCreateFromTmx = false; + + + /// + /// Converts a TiledMapSave to a ReducedTileMapInfo object + /// + /// The TiledMapSave to convert + /// The amount to scale by - default of 1 + /// The zOffset + /// The directory of the file associated with the tiledMapSave, used to find file references. + /// How the files in the .tmx are referenced. + /// + public static ReducedTileMapInfo FromTiledMapSave(TiledMapSave tiledMapSave, float scale, float zOffset, string directory, FileReferenceType referenceType) + { + var toReturn = new ReducedTileMapInfo + { + NumberCellsTall = tiledMapSave.Height, + NumberCellsWide = tiledMapSave.Width + }; + toReturn.CellHeightInPixels = (ushort)tiledMapSave.tileheight; + toReturn.CellWidthInPixels = (ushort)tiledMapSave.tilewidth; + toReturn.QuadHeight = tiledMapSave.tileheight; + toReturn.QuadWidth = tiledMapSave.tilewidth; + + + if (FastCreateFromTmx) + { + CreateFromTiledMapSave(tiledMapSave, directory, referenceType, toReturn); + } + else + { + // slow: + CreateFromSpriteEditorScene(tiledMapSave, scale, zOffset, referenceType, toReturn); + } + + return toReturn; + + + + } + + private static void CreateFromTiledMapSave(TiledMapSave tiledMapSave, string directory, FileReferenceType referenceType, + ReducedTileMapInfo reducedTileMapInfo) + { + ReducedLayerInfo reducedLayerInfo = null; + + for (int i = 0; i < tiledMapSave.MapLayers.Count; i++) + { + var tiledLayer = tiledMapSave.MapLayers[i]; + + string texture = null; + + uint tileIdOfTexture = 0; + Tileset tileSet = null; + uint? firstGid = null; + + if (tiledLayer is MapLayer) + { + var mapLayer = tiledLayer as MapLayer; + + if (mapLayer.data.Length != 0) + { + firstGid = mapLayer.data[0].tiles.FirstOrDefault(item => item != 0); + } + } + else + { + var objectLayer = tiledLayer as mapObjectgroup; + + var firstObjectWithTexture = objectLayer.@object.First(item => item.gid != 0); + + firstGid = firstObjectWithTexture?.gid; + } + + if (firstGid > 0) + { + tileSet = tiledMapSave.GetTilesetForGid(firstGid.Value); + if (tileSet != null) + { + + if (referenceType == FileReferenceType.NoDirectory) + { + texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FlatRedBall.IO.FileManager.RemoveDotDotSlash(directory); + + } + + texture = FlatRedBall.IO.FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + } + + + + int tileWidth = FlatRedBall.Math.MathFunctions.RoundToInt(tiledMapSave.tilewidth); + int tileHeight = FlatRedBall.Math.MathFunctions.RoundToInt(tiledMapSave.tileheight); + + reducedLayerInfo = new ReducedLayerInfo + { + Z = i, + Texture = texture, + Name = tiledLayer.Name, + TileWidth = tileWidth, + TileHeight = tileHeight, + }; + + reducedTileMapInfo.Layers.Add(reducedLayerInfo); + + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + reducedLayerInfo.TextureId = tilesetIndex; + + + // create the quad here: + if (tiledLayer is MapLayer) + { + AddTileLayerTiles(tiledMapSave, reducedLayerInfo, i, tiledLayer, tileSet, tileWidth, tileHeight); + } + + else if (tiledLayer is mapObjectgroup) + { + AddObjectLayerTiles(reducedLayerInfo, tiledLayer, tileSet, firstGid, tileWidth, tileHeight); + } + } + } + } + + static SpriteSave spriteSaveForConversion = new SpriteSave(); + private static void AddTileLayerTiles(TiledMapSave tiledMapSave, ReducedLayerInfo reducedLayerInfo, int i, AbstractMapLayer tiledLayer, Tileset tileSet, int tileWidth, int tileHeight) + { + var asMapLayer = tiledLayer as MapLayer; + var count = asMapLayer.data[0].tiles.Count; + for (int dataId = 0; dataId < count; dataId++) + { + var dataAtIndex = asMapLayer.data[0].tiles[dataId]; + + if (dataAtIndex != 0) + { + + ReducedQuadInfo quad = new DataTypes.ReducedQuadInfo(); + + float tileCenterX; + float tileCenterY; + float tileZ; + + tiledMapSave.CalculateWorldCoordinates(i, dataId, tileWidth, tileHeight, asMapLayer.width, + out tileCenterX, out tileCenterY, out tileZ); + + quad.LeftQuadCoordinate = tileCenterX - tileWidth / 2.0f; + quad.BottomQuadCoordinate = tileCenterY - tileHeight / 2.0f; + + var gid = dataAtIndex; + + //quad.FlipFlags = (byte)((gid & 0xf0000000) >> 28); + + var valueWithoutFlip = gid & 0x0fffffff; + + spriteSaveForConversion.RotationZ = 0; + spriteSaveForConversion.FlipHorizontal = false; + TiledMapSave.SetSpriteTextureCoordinates(gid, spriteSaveForConversion, tileSet, tiledMapSave.orientation); + + + bool isRotated = spriteSaveForConversion.RotationZ != 0; + if (isRotated) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedDiagonallyFlag); + } + + var leftTextureCoordinate = System.Math.Min(spriteSaveForConversion.LeftTextureCoordinate, spriteSaveForConversion.RightTextureCoordinate); + var topTextureCoordinate = System.Math.Min(spriteSaveForConversion.TopTextureCoordinate, spriteSaveForConversion.BottomTextureCoordinate); + + if (spriteSaveForConversion.LeftTextureCoordinate > spriteSaveForConversion.RightTextureCoordinate) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedHorizontallyFlag); + } + + if (spriteSaveForConversion.TopTextureCoordinate > spriteSaveForConversion.BottomTextureCoordinate) + { + quad.FlipFlags = (byte)(quad.FlipFlags | ReducedQuadInfo.FlippedVerticallyFlag); + } + + quad.LeftTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(leftTextureCoordinate * tileSet.Images[0].width); + quad.TopTexturePixel = (ushort)FlatRedBall.Math.MathFunctions.RoundToInt(topTextureCoordinate * tileSet.Images[0].height); + + + if (tileSet.TileDictionary.ContainsKey(valueWithoutFlip - tileSet.Firstgid)) + { + var dictionary = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary; + if (dictionary.ContainsKey("name")) + { + quad.Name = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary["name"]; + } + else if (dictionary.ContainsKey("Name")) + { + quad.Name = tileSet.TileDictionary[valueWithoutFlip - tileSet.Firstgid].PropertyDictionary["Name"]; + } + } + + reducedLayerInfo?.Quads.Add(quad); + } + } + } + + private static void AddObjectLayerTiles(ReducedLayerInfo reducedLayerInfo, AbstractMapLayer tiledLayer, Tileset tileSet, uint? gid, int tileWidth, int tileHeight) + { + var asMapLayer = tiledLayer as mapObjectgroup; + foreach (var objectInstance in asMapLayer.@object) + { + if (objectInstance.gid > 0) + { + ReducedQuadInfo quad = new DataTypes.ReducedQuadInfo(); + + quad.LeftQuadCoordinate = (float)objectInstance.x; + quad.BottomQuadCoordinate = (float)-objectInstance.y; + + quad.RotationDegrees = (float)objectInstance.Rotation; + + quad.FlipFlags = (byte)(gid.Value & 0xf0000000 >> 7); + + var valueWithoutFlip = gid.Value & 0x0fffffff; + + int leftPixelCoord; + int topPixelCoord; + int rightPixelCoord; + int bottomPixelCoord; + TiledMapSave.GetPixelCoordinatesFromGid(gid.Value, tileSet, + out leftPixelCoord, out topPixelCoord, out rightPixelCoord, out bottomPixelCoord); + + quad.LeftTexturePixel = (ushort)Math.Min(leftPixelCoord, rightPixelCoord); + quad.TopTexturePixel = (ushort)Math.Min(topPixelCoord, bottomPixelCoord); + + quad.Name = objectInstance.Name; + if (string.IsNullOrEmpty(quad.Name)) + { + var prop = quad.QuadSpecificProperties.FirstOrDefault(quadProp => quadProp.Name.ToLowerInvariant() == "name"); + quad.Name = (string)prop.Value; + } + + reducedLayerInfo?.Quads.Add(quad); + + } + } + } + + private static void CreateFromSpriteEditorScene(TiledMapSave tiledMapSave, float scale, float zOffset, FileReferenceType referenceType, ReducedTileMapInfo toReturn) + { + var ses = tiledMapSave.ToSceneSave(scale, referenceType); + + // This is not a stable sort! + //ses.SpriteList.Sort((first, second) => first.Z.CompareTo(second.Z)); + ses.SpriteList = ses.SpriteList.OrderBy(item => item.Z).ToList(); + + ReducedLayerInfo reducedLayerInfo = null; + + float z = float.NaN; + + + int textureWidth = 0; + int textureHeight = 0; + + AbstractMapLayer currentLayer = null; + int indexInLayer = 0; + + + foreach (var spriteSave in ses.SpriteList) + { + if (spriteSave.Z != z) + { + indexInLayer = 0; + z = spriteSave.Z; + + + int layerIndex = FlatRedBall.Math.MathFunctions.RoundToInt(z - zOffset); + var abstractMapLayer = tiledMapSave.MapLayers[layerIndex]; + currentLayer = abstractMapLayer; + + reducedLayerInfo = new ReducedLayerInfo + { + Z = spriteSave.Z, + Texture = spriteSave.Texture, + Name = abstractMapLayer.Name, + TileWidth = FlatRedBall.Math.MathFunctions.RoundToInt(spriteSave.ScaleX * 2), + TileHeight = FlatRedBall.Math.MathFunctions.RoundToInt(spriteSave.ScaleY * 2) + }; + + var mapLayer = abstractMapLayer as MapLayer; + // This should have data: + if (mapLayer != null) + { + var idOfTexture = mapLayer.data[0].tiles.FirstOrDefault(item => item != 0); + Tileset tileSet = tiledMapSave.GetTilesetForGid(idOfTexture); + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + + textureWidth = tileSet.Images[0].width; + textureHeight = tileSet.Images[0].height; + + reducedLayerInfo.TextureId = tilesetIndex; + toReturn.Layers.Add(reducedLayerInfo); + } + + + var objectGroup = tiledMapSave.MapLayers[layerIndex] as mapObjectgroup; + + // This code only works based on the assumption that only one tileset will be used in any given object layer's image objects + var mapObjectgroupObject = objectGroup?.@object.FirstOrDefault(o => o.gid != null); + + if (mapObjectgroupObject?.gid != null) + { + var idOfTexture = mapObjectgroupObject.gid.Value; + Tileset tileSet = tiledMapSave.GetTilesetForGid(idOfTexture); + var tilesetIndex = tiledMapSave.Tilesets.IndexOf(tileSet); + + textureWidth = tileSet.Images[0].width; + textureHeight = tileSet.Images[0].height; + reducedLayerInfo.TextureId = tilesetIndex; + toReturn.Layers.Add(reducedLayerInfo); + } + } + + ReducedQuadInfo quad = ReducedQuadInfo.FromSpriteSave(spriteSave, textureWidth, textureHeight); + + if (currentLayer is mapObjectgroup) + { + var asMapObjectGroup = currentLayer as mapObjectgroup; + var objectInstance = asMapObjectGroup.@object[indexInLayer]; + + // skip over any non-sprite objects: + while (objectInstance.gid == null) + { + indexInLayer++; + if (indexInLayer >= asMapObjectGroup.@object.Length) + { + objectInstance = null; + break; + } + else + { + objectInstance = asMapObjectGroup.@object[indexInLayer]; + } + } + + if (objectInstance != null && objectInstance.properties.Count != 0) + { + var nameProperty = objectInstance.properties.FirstOrDefault(item => item.StrippedNameLower == "name"); + if (nameProperty != null) + { + quad.Name = nameProperty.value; + } + else + { + quad.Name = spriteSave.Name; + + bool needsName = string.IsNullOrEmpty(spriteSave.Name); + if (needsName) + { + quad.Name = $"_{currentLayer.Name}runtime{indexInLayer}"; + } + } + + List list = new List(); + + foreach (var property in objectInstance.properties) + { + list.Add( + new NamedValue + { + Name = property.StrippedName, + Value = property.value, + Type = property.Type + } + ); + } + + quad.QuadSpecificProperties = list; + } + } + + reducedLayerInfo?.Quads.Add(quad); + + indexInLayer++; + } + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.cs new file mode 100644 index 000000000..c213b45d6 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/ReducedTileMapInfo.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.IO; +using FlatRedBall.Content; +using FlatRedBall; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + + + +namespace TMXGlueLib.DataTypes +{ + + #region ReducedQuadInfo + + public partial class ReducedQuadInfo + { + public const byte FlippedHorizontallyFlag = 8; + public const byte FlippedVerticallyFlag = 4; + public const byte FlippedDiagonallyFlag = 2; + + public float LeftQuadCoordinate; + public float BottomQuadCoordinate; + + public ushort LeftTexturePixel; + public ushort TopTexturePixel; + + public byte FlipFlags; + + public string Name; + + public float RotationDegrees; + + public List QuadSpecificProperties + { + get; + set; + } + + public static ReducedQuadInfo ReadFrom(BinaryReader reader) + { + ReducedQuadInfo toReturn = new ReducedQuadInfo(); + + toReturn.LeftQuadCoordinate = reader.ReadSingle(); + toReturn.BottomQuadCoordinate = reader.ReadSingle(); + toReturn.LeftTexturePixel = reader.ReadUInt16(); + toReturn.TopTexturePixel = reader.ReadUInt16(); + + toReturn.Name = reader.ReadString(); + + toReturn.FlipFlags = reader.ReadByte(); + + return toReturn; + } + + + public void WriteTo(BinaryWriter writer) + { + writer.Write(LeftQuadCoordinate); + writer.Write(BottomQuadCoordinate); + writer.Write(LeftTexturePixel); + writer.Write(TopTexturePixel); + + writer.Write(Name); + + writer.Write(FlipFlags); + } + + + public override string ToString() + { + return Name + " " + LeftQuadCoordinate + " " + BottomQuadCoordinate; + } + } + + #endregion + + #region ReducedLayerInfo + + public class ReducedLayerInfo + { + public string Texture; + public string Name; + + public uint NumberOfQuads; + + public float Z; + + // Each tileset can have different widths and heights. + // Currently we only allow one tileset per layer, so we + // can store that width and height here + public int TileWidth; + public int TileHeight; + + public List Quads = new List(); + + // Version 2: + public int TextureId; + + + public static ReducedLayerInfo ReadFrom(BinaryReader reader, int version) + { + ReducedLayerInfo toReturn = new ReducedLayerInfo(); + + toReturn.Z = reader.ReadSingle(); + + toReturn.Texture = reader.ReadString(); + + toReturn.Name = reader.ReadString(); + + toReturn.NumberOfQuads = reader.ReadUInt32(); + + for(int i = 0; i < toReturn.NumberOfQuads; i++) + { + toReturn.Quads.Add( ReducedQuadInfo.ReadFrom(reader)); + } + + if(version >= 2) + { + toReturn.TextureId = reader.ReadInt32(); + } + + return toReturn; + } + + public void WriteTo(BinaryWriter writer, int version) + { + writer.Write(Z); + + writer.Write(Texture); + + writer.Write(Name); + + NumberOfQuads = (uint)Quads.Count; + writer.Write(Quads.Count); + + for (int i = 0; i < NumberOfQuads; i++) + { + Quads[i].WriteTo(writer); + } + + if (version >= 2) + { + writer.Write(TextureId); + } + } + + public override string ToString() + { + return Texture + " (" + Quads.Count + ")"; + } + } + + #endregion + + #region ReducedTileMapInfo + + + public partial class ReducedTileMapInfo + { + public ushort CellWidthInPixels; + public ushort CellHeightInPixels; + + public float QuadWidth; + public float QuadHeight; + + public uint NumberOfLayers; + + + // Version 0: + // Initial version when versioning was tracked. + // Version 1: + // Added: + // int NumberCellsWide; + // int NumberCellsTall; + public int VersionNumber = 2; + + public int NumberCellsWide; + public int NumberCellsTall; + + public List Layers = new List(); + + public static ReducedTileMapInfo ReadFrom(BinaryReader reader) + { + ReducedTileMapInfo toReturn = new ReducedTileMapInfo(); + + toReturn.VersionNumber = reader.ReadInt32(); + + toReturn.CellWidthInPixels = reader.ReadUInt16(); + toReturn.CellHeightInPixels = reader.ReadUInt16(); + + toReturn.QuadHeight = reader.ReadSingle(); + toReturn.QuadWidth = reader.ReadSingle(); + + toReturn.NumberOfLayers = reader.ReadUInt32(); + + for (int i = 0; i < toReturn.NumberOfLayers; i++) + { + + toReturn.Layers.Add(ReducedLayerInfo.ReadFrom(reader, toReturn.VersionNumber)); + } + + // Version 1: + if(toReturn.VersionNumber > 0) + { + toReturn.NumberCellsWide = reader.ReadInt32(); + toReturn.NumberCellsTall = reader.ReadInt32(); + } + + + return toReturn; + } + + public void WriteTo(BinaryWriter writer) + { + writer.Write(VersionNumber); + + writer.Write(CellWidthInPixels); + writer.Write(CellHeightInPixels); + + writer.Write(QuadHeight); + writer.Write(QuadWidth); + + NumberOfLayers = (uint)Layers.Count; + writer.Write(NumberOfLayers); + + for (int i = 0; i < NumberOfLayers; i++) + { + this.Layers[i].WriteTo(writer, VersionNumber); + } + + // Version 1: + if(VersionNumber > 0) + { + writer.Write(NumberCellsWide); + writer.Write(NumberCellsTall); + } + + } + + public static ReducedTileMapInfo FromFile(string fileName) + { + ReducedTileMapInfo rtmi = null; + using (Stream inputStream = FileManager.GetStreamForFile(fileName)) + using (BinaryReader binaryReader = new BinaryReader(inputStream)) + { + rtmi = ReducedTileMapInfo.ReadFrom(binaryReader); + + } + + return rtmi; + } + public override string ToString() + { + return this.Layers.Count.ToString(CultureInfo.InvariantCulture); + } + + public List GetReferencedFiles() + { + List toReturn = new List(); + + foreach (var item in Layers) + { + toReturn.Add(item.Texture); + + } + + return toReturn; + } + } + + #endregion +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimation.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimation.cs new file mode 100644 index 000000000..b0deab311 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimation.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + public class TileAnimation + { + [XmlElement("frame")] + public List Frames + { + get; + set; + } + + public TileAnimation() + { + Frames = new List(); + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimationFrame.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimationFrame.cs new file mode 100644 index 000000000..008ae23ac --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileAnimationFrame.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace TMXGlueLib +{ + [XmlRoot("frame")] + public class TileAnimationFrame + { + [XmlAttribute("tileid")] + public int TileId + { + get; + set; + } + + [XmlAttribute("duration")] + public int Duration + { + get; + set; + } + + + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileNodeNetworkCreator.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileNodeNetworkCreator.cs new file mode 100644 index 000000000..73803e435 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TileNodeNetworkCreator.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FlatRedBall.TileGraphics; +using FlatRedBall.Math; + +namespace FlatRedBall.AI.Pathfinding +{ + public static class TileNodeNetworkCreator + { + public static TileNodeNetwork CreateFrom(LayeredTileMap layeredTileMap, DirectionalType directionalType, + Func, bool> predicate) + { + var numberOfTilesWide = + MathFunctions.RoundToInt(layeredTileMap.Width / layeredTileMap.WidthPerTile.Value); + var numberOfTilesTall = + MathFunctions.RoundToInt(layeredTileMap.Height / layeredTileMap.HeightPerTile.Value); + + var tileWidth = layeredTileMap.WidthPerTile.Value; + + var dimensionHalf = tileWidth / 2.0f; + + TileNodeNetwork nodeNetwork = new TileNodeNetwork( + 0 + dimensionHalf, + -layeredTileMap.Height + tileWidth / 2.0f, + tileWidth, + numberOfTilesWide, + numberOfTilesTall, + directionalType); + + + var properties = layeredTileMap.TileProperties; + + foreach (var kvp in properties) + { + string name = kvp.Key; + var namedValues = kvp.Value; + + if (predicate(namedValues)) + { + foreach (var layer in layeredTileMap.MapLayers) + { + var dictionary = layer.NamedTileOrderedIndexes; + + if (dictionary.ContainsKey(name)) + { + var indexList = dictionary[name]; + + foreach (var index in indexList) + { + float left; + float bottom; + layer.GetBottomLeftWorldCoordinateForOrderedTile(index, out left, out bottom); + + var centerX = left + dimensionHalf; + var centerY = bottom + dimensionHalf; + + nodeNetwork.AddAndLinkTiledNodeWorld(centerX, centerY); + } + } + } + } + } + + nodeNetwork.Visible = true; + + return nodeNetwork; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Conversion.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Conversion.cs new file mode 100644 index 000000000..2da93e0da --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Conversion.cs @@ -0,0 +1,1392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using System.Threading.Tasks; +using FlatRedBall; +using FlatRedBall.AI.Pathfinding; +using FlatRedBall.Content; +using FlatRedBall.Content.AI.Pathfinding; +using FlatRedBall.Content.Math.Geometry; +using FlatRedBall.Content.Polygon; +using FlatRedBall.Content.Scene; +using FlatRedBall.IO; +using FlatRedBall.Math.Geometry; +using System.Globalization; + +namespace TMXGlueLib +{ + #region FileReferenceType enum + public enum FileReferenceType + { + NoDirectory, + Absolute, + Relative + } + #endregion + + public partial class TiledMapSave + { + #region Enums + + public enum CSVPropertyType { Tile, Layer, Map, Object }; + + enum LessOrGreaterDesired + { + Less, + Greater, + NoChange + } + #endregion + + #region Fields + + public static LayerVisibleBehavior LayerVisibleBehaviorValue = LayerVisibleBehavior.Ignore; + public static int MaxDegreeOfParallelism = 1; + + const string animationColumnName = "EmbeddedAnimation (List)"; + + + private static Tuple _offset = new Tuple(0f, 0f, 0f); + + #endregion + + #region Properties + + public static Tuple Offset + { + get { return _offset; } + set { _offset = value; } + } + + #endregion + + public Scene ToScene(string contentManagerName, float scale) + { + var scene = ToSceneSave(scale); + return scene.ToScene(contentManagerName); + } + + /// + /// The FRB plugin uses the properties dictionary to create objects and assign their values. + /// This moves the Type value to the properties so that it can be used later on to create entities. + /// Technically this may cause problems if there is a custom property called Type, but we'll cross that + /// in the future if it ever becomes a problem. + /// + public void MoveTypeToProperties() + { + foreach (var tileset in this.Tilesets) + { + var tilesWithTypes = tileset.Tiles.Where(item => !string.IsNullOrEmpty(item.Type)); + + foreach (var tile in tilesWithTypes) + { + var dictionaryEntry = tileset.TileDictionary[(uint)tile.id]; + + dictionaryEntry.properties.Add(new property { name = "Type", value = tile.Type }); + } + } + + foreach (var objectLayer in this.objectgroup) + { + if (objectLayer.@object != null) + { + foreach (var item in objectLayer.@object) + { + if (item.gid != null) + { + var tileset = GetTilesetForGid(item.gid.Value); + + // todo - need to modify the TMX loader to support reading the Type from an object. Right now it works + // if the type is on the tile in the tileset, but not on the object. But...I'm tired. That will have to + // be something I add later. + + if (tileset.TileDictionary.ContainsKey(item.gid.Value - tileset.Firstgid)) + { + var properties = tileset.TileDictionary[item.gid.Value - tileset.Firstgid]; + if (!string.IsNullOrEmpty(properties.Type)) + { + + item.properties.Add(new property { name = "Type", Type = "string", value = properties.Type }); + item.PropertyDictionary["Type"] = properties.Type; + } + } + + } + } + } + } + + } + + public void NameUnnamedTilesetTiles() + { + foreach (var tileset in this.Tilesets) + { + foreach (var tileDictionary in tileset.TileDictionary) + { + var propertyList = tileDictionary.Value.properties; + var nameProperty = propertyList.FirstOrDefault(item => item.StrippedNameLower == "name"); + + if (nameProperty == null) + { + // create a new property: + var newNameProperty = new property(); + newNameProperty.name = "Name"; + newNameProperty.value = tileset.Name + tileDictionary.Key + "_autoname"; + + propertyList.Add(newNameProperty); + + tileDictionary.Value.ForceRebuildPropertyDictionary(); + } + } + } + } + + public void NameUnnamedObjects() + { + int index = 0; + foreach (var objectLayer in this.objectgroup) + { + // Seems like this can be null, not sure why... + if (objectLayer.@object != null) + { + + foreach (var objectInstance in objectLayer.@object) + { + bool hasName = string.IsNullOrEmpty(objectInstance.Name) == false; + bool hasNameProperty = objectInstance.properties.Any(item => item.StrippedNameLower == "name"); + + if (!hasName && !hasNameProperty) + { + objectInstance.Name = $"object{index}_autoname"; + objectInstance.properties.Add(new TMXGlueLib.property { name = "name", value = objectInstance.Name }); + index++; + + } + else if (hasName && !hasNameProperty) + { + objectInstance.properties.Add(new TMXGlueLib.property { name = "name", value = objectInstance.Name }); + } + } + } + } + } + + public string ToCSVString(CSVPropertyType type = CSVPropertyType.Tile, string layerName = null) + { + var sb = new StringBuilder(); + IEnumerable columnsAsEnumerable = GetColumnNames(type, layerName); + var columnList = columnsAsEnumerable as IList ?? columnsAsEnumerable.ToList(); + WriteColumnHeader(sb, columnList); + WriteColumnValues(sb, columnList, type, layerName); + + return sb.ToString(); + } + + private void WriteColumnValues(StringBuilder sb, IList columnNames, CSVPropertyType type, string layerName) + { + columnNames = columnNames.Select(item => property.GetStrippedName(item)).ToList(); + + + // TODO: There is probably a good way to refactor this code + switch (type) + { + case CSVPropertyType.Tile: + WriteColumnValuesForTile(sb, columnNames); + break; + case CSVPropertyType.Layer: + + WriteColumnValuesForLayer(sb, columnNames, layerName); + break; + case CSVPropertyType.Map: + WriteValuesFromDictionary(sb, null, PropertyDictionary, columnNames, null); + break; + case CSVPropertyType.Object: + this.objectgroup.Where( + og => + layerName == null || + (((AbstractMapLayer)og).Name != null && ((AbstractMapLayer)og).Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))) + .SelectMany(o => o.@object, (o, c) => new { group = o, obj = c, X = c.x, Y = c.y }) + .Where(o => o.obj.gid != null) + .ToList() + .ForEach(o => WriteValuesFromDictionary(sb, o.group.PropertyDictionary, o.obj.PropertyDictionary, columnNames, null)); + break; + } + } + + private void WriteColumnValuesForLayer(StringBuilder sb, IList columnNames, string layerName) + { + var availableItems = + this.Layers.Where( + l => + layerName == null || + (l.Name != null && l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))).ToList(); + + foreach (var l in availableItems) + { + WriteValuesFromDictionary(sb, null, l.PropertyDictionary, columnNames, null); + } + + } + + private void WriteColumnValuesForTile(StringBuilder sb, IList columnNames) + { + for (int i = 0; i < this.Tilesets.Count; i++) + { + Tileset tileSet = this.Tilesets[i]; + + if (tileSet.Tiles != null) + { + Func predicate = + t => t.PropertyDictionary.Count > 0 || + (t.Animation != null && t.Animation.Frames != null && t.Animation.Frames.Count > 0); + + foreach (mapTilesetTile tile in tileSet.Tiles.Where(predicate)) + { + Dictionary propertyDictionary = new Dictionary(tile.PropertyDictionary); + + bool needsName = propertyDictionary.Count != 0 || + (tile.Animation != null && tile.Animation.Frames != null && tile.Animation.Frames.Count != 0); + + if (needsName && propertyDictionary.Keys.Any(item => property.GetStrippedName(item).ToLowerInvariant() == "name") == false) + { + var globalId = tile.id + tileSet.Firstgid; + // This has properties, but no name, so let's give it a name! + propertyDictionary.Add("Name (required, string)", "Unnamed" + globalId); + } + WriteValuesFromDictionary(sb, null, propertyDictionary, columnNames, tile.Animation, i); + } + } + } + foreach (var objectGroup in this.objectgroup) + { + foreach (var @object in objectGroup.@object) + { + if (@object.gid != null) + { + WriteValuesFromDictionary(sb, null, @object.PropertyDictionary, columnNames, null); + } + } + } + } + + static int numberOfUnnamedTiles = 0; + + private void WriteValuesFromDictionary(StringBuilder sb, IDictionary pDictionary, + IDictionary iDictionary, IEnumerable columnNames, TileAnimation animation, int tilesetIndex = 0) + { + + + ///////////////////// Early out ////////////////////// + + if (tilesetIndex >= Tilesets.Count) + { + return; + } + ////////////////// End early out //////////////////// + uint startGid = Tilesets[tilesetIndex].Firstgid; + + string nameValue = GetNameValue(iDictionary); + + List row = new List(); + row.Add(nameValue); + + int layerIndex = -1; + + + + uint endIdExclusive = uint.MaxValue; + if (tilesetIndex < Tilesets.Count - 1) + { + endIdExclusive = Tilesets[tilesetIndex + 1].Firstgid; + } + + + for (int i = 0; i < Layers.Count; i++) + { + var layer = Layers[i]; + // see if any layers reference this tile: + foreach (var data in layer.data) + { + foreach (var tile in data.tiles) + { + if (tile >= startGid && tile < endIdExclusive) + { + layerIndex = i; + break; + } + } + + if (layerIndex != -1) + { + break; + } + } + + if (layerIndex != -1) + { + break; + } + } + + bool hasAnimation = columnNames.Contains("EmbeddedAnimation"); + + if (hasAnimation) + { + AddAnimationFrameAtIndex(animation, row, 0, layerIndex, tilesetIndex); + } + AppendCustomProperties(pDictionary, iDictionary, columnNames, row, false); + + AppendRowToStringBuilder(sb, row); + + if (animation != null && animation.Frames != null) + { + + for (int i = 1; i < animation.Frames.Count; i++) + { + row = new List(); + row.Add(""); // Name column + + + if (hasAnimation) + { + AddAnimationFrameAtIndex(animation, row, i, layerIndex, tilesetIndex); + } + AppendCustomProperties(pDictionary, iDictionary, columnNames, row, true); + AppendRowToStringBuilder(sb, row); + } + } + } + + private void AddAnimationFrameAtIndex(TileAnimation animation, List row, int animationIndex, int indexOfLayerReferencingTileset, int tilesetIndex) + { + if (animation != null && animation.Frames != null && animation.Frames.Count > animationIndex) + { + // public int TileId + // public int Duration + + var frame = animation.Frames[animationIndex]; + + int leftCoordinate = 0; + int rightCoordinate = 16; + int topCoordinate = 0; + int bottomCoordinate = 16; + + var frameId = (uint)frame.TileId; + // not sure why, but need to add 1: + //frameId++; + // Update - I know why, because the TileId + // is relative to the Tileset. I didn't try + // this with multiple tilesets, and the first + // tileset has a starting ID of 1. + frameId += this.Tilesets[tilesetIndex].Firstgid; + + GetPixelCoordinatesFromGid(frameId, this.Tilesets[tilesetIndex], + out leftCoordinate, out topCoordinate, out rightCoordinate, out bottomCoordinate); + + row.Add(string.Format( + "new FlatRedBall.Content.AnimationChain.AnimationFrameSaveBase(TextureName={0}, " + + "FrameLength={1}, LeftCoordinate={2}, RightCoordinate={3}, TopCoordinate={4}, BottomCoordinate={5})", + indexOfLayerReferencingTileset, + (frame.Duration / 1000.0f).ToString(CultureInfo.InvariantCulture), + leftCoordinate.ToString(CultureInfo.InvariantCulture), + rightCoordinate.ToString(CultureInfo.InvariantCulture), + topCoordinate.ToString(CultureInfo.InvariantCulture), + bottomCoordinate.ToString(CultureInfo.InvariantCulture))); + } + else + { + row.Add(null); + } + } + + private static void AppendRowToStringBuilder(StringBuilder sb, List row) + { + bool isFirst = true; + foreach (var originalValue in row) + { + string value = originalValue; + + if (!isFirst) + { + sb.Append(","); + + } + if (value != null) + { + value = value.Replace("\"", "\"\""); + } + + sb.AppendFormat("\"{0}\"", value); + isFirst = false; + } + sb.AppendLine(); + + } + + private static string GetNameValue(IDictionary iDictionary) + { + string nameValue = null; + + bool doesDictionaryContainNameValue = + iDictionary.Any(p => property.GetStrippedName(p.Key).Equals("name", StringComparison.CurrentCultureIgnoreCase)); + + if (doesDictionaryContainNameValue) + { + nameValue = iDictionary.First(p => property.GetStrippedName(p.Key).Equals("name", StringComparison.CurrentCultureIgnoreCase)).Value; + } + else + { + nameValue = "UnnamedTile" + numberOfUnnamedTiles; + numberOfUnnamedTiles++; + } + return nameValue; + } + + private static void AppendCustomProperties(IDictionary pDictionary, IDictionary iDictionary, IEnumerable columnNames, List row, bool forceEmpty) + { + foreach (string columnName in columnNames) + { + string strippedColumnName = property.GetStrippedName(columnName); + + bool isAnimation = + strippedColumnName.Equals("embeddedanimation", StringComparison.CurrentCultureIgnoreCase); + + bool isCustomProperty = !isAnimation && + !strippedColumnName.Equals("name", StringComparison.CurrentCultureIgnoreCase); + + + + if (isCustomProperty) + { + if (!forceEmpty && iDictionary.Any(p => property.GetStrippedName(p.Key).Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase))) + { + var value = + iDictionary.First(p => property.GetStrippedName(p.Key).Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase)).Value; + + row.Add(value); + } + // Victor Chelaru + // October 12, 2014 + // Not sure what pDictionary + // is, but it looks like it's + // only used for "object" CSVs. + // My first question is - do we need + // to use stripped names here? Also, do + // we even want to support object dictionaries + // in the future? How does this fit in with the + // new "level" pattern. + else if (!forceEmpty && pDictionary != null && pDictionary.Any(p => p.Key.Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase))) + { + var value = + pDictionary.First(p => p.Key.Equals(strippedColumnName, StringComparison.CurrentCultureIgnoreCase)).Value; + + row.Add(value); + } + else + { + row.Add(null); + } + } + } + } + + private static void WriteColumnHeader(StringBuilder sb, IEnumerable columnNames) + { + sb.Append("Name (required)"); + foreach (string columnName in columnNames) + { + string strippedName = property.GetStrippedName(columnName); + + + bool isName = strippedName.Equals("name", StringComparison.CurrentCultureIgnoreCase); + + if (!isName) + { + // Update August 27, 2012 + // We can't just assume that + // all of the column names are + // going to be capitalized. This + // was likely done to force the Name + // property to be capitalized, which we + // want, but we don't want to do it for everything. + //if (columnName.Length > 1) + //{ + // sb.AppendFormat(",{0}{1}", columnName.Substring(0, 1).ToUpper(), columnName.Substring(1)); + //} + //else + //{ + // sb.AppendFormat(",{0}", columnName.ToUpper()); + //} + sb.Append("," + columnName); + } + } + sb.AppendLine(); + } + + /// + /// Compares the stripped name of properties - removing the type + /// + public class CaseInsensitivePropertyEqualityComparer : IEqualityComparer + { + public bool Equals(string x, string y) + { + + return property.GetStrippedName(x).Equals(property.GetStrippedName(y), StringComparison.CurrentCultureIgnoreCase); + } + + public int GetHashCode(string obj) + { + return property.GetStrippedName(obj).ToLowerInvariant().GetHashCode(); + } + } + + private IEnumerable GetColumnNames(CSVPropertyType type, string layerName) + { + var comparer = new CaseInsensitivePropertyEqualityComparer(); + + var columnNames = new HashSet(); + + switch (type) + { + case CSVPropertyType.Tile: + return GetColumnNamesForTile(comparer); + case CSVPropertyType.Layer: + return + this.Layers.Where( + l => + layerName == null || + (l.Name != null && l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))) + .SelectMany(l => l.PropertyDictionary) + .Select(d => d.Key) + .Distinct(comparer); + case CSVPropertyType.Map: + return this.PropertyDictionary.Select(d => d.Key).Distinct(comparer); + case CSVPropertyType.Object: + + List toReturn = new List(); + + toReturn.Add("X (int)"); + toReturn.Add("Y (int)"); + + if (objectgroup != null) + { + + var query1 = + objectgroup.Where(l => + layerName == null || + (l.Name != null && + l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))); + var query2 = + objectgroup.Where(l => + layerName == null || + (l.Name != null && + l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase))); + return toReturn + .Union(query1 + .SelectMany(o => o.@object) + .Where(o => o.gid != null) //November 2015 by Jesse Crafts-Finch: will ignore objects which are to be treated as sprites (they have a gid). + .SelectMany(o => o.PropertyDictionary) + .Select(d => d.Key), comparer) + .Union(query2 + .SelectMany(o => o.PropertyDictionary) + .Select(d => d.Key), comparer); + } + else + { + return toReturn; + } + + } + return columnNames; + } + + private IEnumerable GetColumnNamesForTile(CaseInsensitivePropertyEqualityComparer comparer) + { + List toReturn = new List(); + + // Name is required and always available + toReturn.Add("Name (string, required)"); + + // And animation is required too + toReturn.Add(animationColumnName); + + toReturn.AddRange(this.Tilesets.SelectMany(t => t.Tiles) + .SelectMany(tile => tile.PropertyDictionary) + .Select(d => d.Key) + //.Distinct(comparer) + .ToList()); + + foreach (var group in this.objectgroup) + { + bool addedGroup = false; + foreach (var @object in group.@object) + { + if (@object.gid != null) + { + addedGroup = true; + toReturn.AddRange(@object.PropertyDictionary.Keys); + } + } + if (addedGroup) + { + toReturn.AddRange(group.PropertyDictionary.Keys); + } + } + + return toReturn.Distinct(comparer); + } + + + public NodeNetwork ToNodeNetwork(bool requireTile = true) + { + return ToNodeNetwork(true, true, true, requireTile); + } + + public NodeNetworkSave ToNodeNetworkSave(bool linkHorizontally, bool linkVertically, bool linkDiagonally, bool requireTile) + { + NodeNetwork nodeNetwork = ToNodeNetwork(linkHorizontally, linkVertically, linkDiagonally, requireTile); + return NodeNetworkSave.FromNodeNetwork(nodeNetwork); + } + + public NodeNetworkSave ToNodeNetworkSave(bool requireTile = true) + { + return ToNodeNetworkSave(true, true, true, requireTile); + } + + public NodeNetwork ToNodeNetwork(bool linkHorizontally, bool linkVertically, bool linkDiagonally, bool requireTile) + { + var toReturn = new NodeNetwork(); + + + int layercount = 0; + foreach (MapLayer mapLayer in this.Layers) + { + if (!mapLayer.IsVisible) + { + switch (mapLayer.VisibleBehavior) + { + case LayerVisibleBehavior.Ignore: + break; + case LayerVisibleBehavior.Skip: + continue; + } + } + var allNodes = new Dictionary>>(); + allNodes[layercount] = new Dictionary>(); + + + MapLayer mLayer = mapLayer; + int mLayerCount = layercount; + Parallel.For(0, mapLayer.data[0].tiles.Count, count => + { + uint gid = mLayer.data[0].tiles[count]; + + Tileset tileSet = GetTilesetForGid(gid); + if (tileSet != null || !requireTile) + { + var node = new PositionedNode(); + + //int tileWidth = requireTile ? tileSet.tilewidth : tilewidth; + //int tileHeight = requireTile ? tileSet.tileheight : tileheight; + int x = count % this.Width; + int y = count / this.Width; + + float nodex; + float nodey; + float nodez; + + CalculateWorldCoordinates(mLayerCount, count, tilewidth, tileheight, mLayer.width, out nodex, out nodey, out nodez); + + node.X = nodex; + node.Y = nodey; + node.Z = nodez; + + lock (allNodes) + { + if (!allNodes[mLayerCount].ContainsKey(x)) + { + allNodes[mLayerCount][x] = new Dictionary(); + } + + allNodes[mLayerCount][x][y] = node; + } + node.Name = string.Format("Node {0}", count); + lock (toReturn) + { + toReturn.AddNode(node); + } + } + }); + SetupNodeLinks(linkHorizontally, linkVertically, linkDiagonally, allNodes[layercount]); + + RemoveExcludedNodesViaPolygonLayer(toReturn, mapLayer, allNodes[layercount]); + LowerNodesInNodesDownShapeCollection(mapLayer, allNodes[layercount]); + RaiseNodesInNodesUpShapeCollection(mapLayer, allNodes[layercount]); + + ++layercount; + } + toReturn.UpdateShapes(); + + return toReturn; + } + + private void RaiseNodesInNodesUpShapeCollection(MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nodesup"); + List nodesToMoveUp = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToMoveUp) + { + node.Z += .001f; + } + } + + private void LowerNodesInNodesDownShapeCollection(MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nodesdown"); + List nodesToMoveDown = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToMoveDown) + { + node.Z -= .001f; + } + } + + private void RemoveExcludedNodesViaPolygonLayer(NodeNetwork nodeNetwork, MapLayer mapLayer, Dictionary> allNodes) + { + ShapeCollection sc = this.ToShapeCollection(mapLayer.Name + " nonodes"); + List nodesToRemove = GetNodesThatCollideWithShapeCollection(sc, allNodes); + + foreach (var node in nodesToRemove) + { + nodeNetwork.Remove(node); + } + } + + private List GetNodesThatCollideWithShapeCollection(ShapeCollection sc, Dictionary> allNodes) + { + var returnValue = new List(); + + if (sc != null && sc.Polygons != null) + { + foreach (Polygon polygon in sc.Polygons) + { + polygon.ForceUpdateDependencies(); + } + + foreach (var xpair in allNodes) + { + foreach (var ypair in xpair.Value) + { + PositionedNode node = ypair.Value; + var rectangle = new AxisAlignedRectangle { Position = node.Position, ScaleX = 1, ScaleY = 1 }; + + if (sc.CollideAgainst(rectangle)) + { + returnValue.Add(node); + } + } + } + } + return returnValue; + } + + private static void SetupNodeLinks(bool linkHorizontally, bool linkVertically, bool linkDiagonally, Dictionary> allNodes) + { + foreach (var xpair in allNodes) + { + int x = xpair.Key; + foreach (var ypair in xpair.Value) + { + int y = ypair.Key; + + if (linkVertically && allNodes.ContainsKey(x - 1) && allNodes[x - 1].ContainsKey(y)) + { + float cost = (ypair.Value.Position - allNodes[x - 1][y].Position).Length(); + ypair.Value.LinkTo(allNodes[x - 1][y], cost); + } + if (linkHorizontally && xpair.Value.ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - xpair.Value[y - 1].Position).Length(); + ypair.Value.LinkTo(xpair.Value[y - 1], cost); + } + if (linkDiagonally && allNodes.ContainsKey(x - 1) && allNodes[x - 1].ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - allNodes[x - 1][y - 1].Position).Length(); + ypair.Value.LinkTo(allNodes[x - 1][y - 1], cost); + } + if (linkDiagonally && allNodes.ContainsKey(x + 1) && allNodes[x + 1].ContainsKey(y - 1)) + { + float cost = (ypair.Value.Position - allNodes[x + 1][y - 1].Position).Length(); + ypair.Value.LinkTo(allNodes[x + 1][y - 1], cost); + } + } + } + } + + public SceneSave ToSceneSave(float scale, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + var toReturn = new SceneSave { CoordinateSystem = FlatRedBall.Math.CoordinateSystem.RightHanded }; + + // TODO: Somehow add all layers separately + for (int layercount = 0; layercount < this.MapLayers.Count; layercount++) + { + var abstractMapLayer = this.MapLayers[layercount]; + var mapLayer = abstractMapLayer as MapLayer; + + if (mapLayer != null) + { + if (!mapLayer.IsVisible) + { + switch (mapLayer.VisibleBehavior) + { + case LayerVisibleBehavior.Ignore: + break; + case LayerVisibleBehavior.Skip: + continue; + } + } + + MapLayer mLayer = mapLayer; + int mLayerCount = layercount; + + for (int i = 0; i < mapLayer.data[0].tiles.Count; i++) + { + uint gid = mLayer.data[0].tiles[i]; + if (gid > 0) + { + Tileset tileSet = GetTilesetForGid(gid); + if (tileSet != null) + { + SpriteSave sprite = CreateSpriteSaveFromMapTileset(scale, mLayerCount, mLayer, i, gid, + tileSet, referenceType); + lock (toReturn) + { + toReturn.SpriteList.Add(sprite); + } + } + } + } + continue; + } + + var group = abstractMapLayer as mapObjectgroup; + bool shouldProcess = group?.@object != null && group.Visible && !string.IsNullOrEmpty(group.Name); + if (shouldProcess) //&& (string.IsNullOrEmpty(layerName) || group.name.Equals(layerName))) + { + foreach (mapObjectgroupObject @object in @group.@object) + { + if (@object.gid != null) + { + SpriteSave sprite = CreateSpriteSaveFromObject(scale, @object, layercount, referenceType); + lock (toReturn) + { + toReturn.SpriteList.Add(sprite); + } + } + } + } + } + + return toReturn; + } + + private SpriteSave CreateSpriteSaveFromObject(float scale, mapObjectgroupObject @object, int layerCount, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + + if (@object.gid == null) + { + throw new NotSupportedException("CreateSpriteSaveFromObject called on a non image object. gid not set."); + } + + var gid = @object.gid.Value; + + Tileset tileSet = GetTilesetForGid(gid); + + var sprite = new SpriteSave(); + //if (!mapLayer.IsVisible && mapLayer.VisibleBehavior == LayerVisibleBehavior.Match) + //{ + // sprite.Visible = false; + //} + + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + // TODO: only calculate these once per tileset. Perhaps it can be done in the deserialize method + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + //int tilesHigh = (imageHeight - margin) / (tileHeight + spacing); + + if (referenceType == FileReferenceType.NoDirectory) + { + sprite.Texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + string directory = FileManager.GetDirectory(this.FileName); + + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FileManager.RemoveDotDotSlash(directory); + + } + + sprite.Texture = FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + + uint tileTextureRelativeToStartOfTileset = + (0x0fffffff & gid) - tileSet.Firstgid + 1; + + //if (tileSet.TileDictionary.ContainsKey(tileTextureRelativeToStartOfTileset)) + //{ + // var dictionary = tileSet.TileDictionary[tileTextureRelativeToStartOfTileset].PropertyDictionary; + + // foreach (var kvp in dictionary) + // { + // var key = kvp.Key; + + // if (IsName(key)) + // { + // sprite.Name = kvp.Value; + // } + // } + //} + + // This is bad - we want to ue the same names as in Tiled so we don't accidentally + // apply properties from the wrong tiles + //if (string.IsNullOrEmpty(@object.Name)) + //{ + // sprite.Name = "Unnamed" + gid; + //} + //else + { + sprite.Name = @object.Name; + } + SetSpriteTextureCoordinates(gid, sprite, tileSet, this.orientation); + + //CalculateWorldCoordinates(layercount, tileIndex, tileWidth, tileHeight, this.Width, out sprite.X, out sprite.Y, out sprite.Z); + sprite.X = (float)@object.x; + sprite.Y = -(float)@object.y; + sprite.Z = layerCount; + + //sprite.ScaleX = tileWidth / 2.0f; + //sprite.ScaleY = tileHeight / 2.0f; + sprite.ScaleX = (float)@object.width / 2.0f; + sprite.ScaleY = (float)@object.height / 2.0f; + + ///Is the tileset offset necessary for this? + //if (tileSet.Tileoffset != null && tileSet.Tileoffset.Length == 1) + //{ + // sprite.X += tileSet.Tileoffset[0].x; + // sprite.Y -= tileSet.Tileoffset[0].y; + //} + + sprite.X *= scale; + sprite.Y *= scale; + // Update August 28, 2012 + // The TMX converter splits + // the Layers by their Z values. + // We want each Layer to have its + // own explicit Z value, so we don't + // want to adjust the Z's when we scale: + //sprite.Z *= scale; + + sprite.ScaleX *= scale; + sprite.ScaleY *= scale; + + sprite.X += sprite.ScaleX; + sprite.Y += sprite.ScaleY; + return sprite; + } + private SpriteSave CreateSpriteSaveFromMapTileset(float scale, int layercount, MapLayer mapLayer, int tileIndex, uint gid, Tileset tileSet, FileReferenceType referenceType = FileReferenceType.NoDirectory) + { + var sprite = new SpriteSave(); + if (!mapLayer.IsVisible && mapLayer.VisibleBehavior == LayerVisibleBehavior.Match) + { + sprite.Visible = false; + } + + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + // TODO: only calculate these once per tileset. Perhaps it can be done in the deserialize method + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + //int tilesHigh = (imageHeight - margin) / (tileHeight + spacing); + + if (referenceType == FileReferenceType.NoDirectory) + { + sprite.Texture = tileSet.Images[0].sourceFileName; + } + else if (referenceType == FileReferenceType.Absolute) + { + string directory = FileManager.GetDirectory(this.FileName); + + if (!string.IsNullOrEmpty(tileSet.SourceDirectory) && tileSet.SourceDirectory != ".") + { + directory += tileSet.SourceDirectory; + + directory = FileManager.RemoveDotDotSlash(directory); + + } + + sprite.Texture = FileManager.RemoveDotDotSlash(directory + tileSet.Images[0].Source); + + } + else + { + throw new NotImplementedException(); + } + + uint tileTextureRelativeToStartOfTileset = + (0x0fffffff & gid) - tileSet.Firstgid; + + if (tileSet.TileDictionary.ContainsKey(tileTextureRelativeToStartOfTileset)) + { + var dictionary = tileSet.TileDictionary[tileTextureRelativeToStartOfTileset].PropertyDictionary; + + foreach (var kvp in dictionary) + { + var key = kvp.Key; + + if (IsName(key)) + { + sprite.Name = kvp.Value; + } + } + } + + // This can cause tiles to use properties from other tiles, so this is bad. If no name exists in Tiled, + // no name should be given here: + //if (string.IsNullOrEmpty(sprite.Name)) + //{ + // sprite.Name = "Unnamed" + gid; + //} + + SetSpriteTextureCoordinates(gid, sprite, tileSet, this.orientation); + CalculateWorldCoordinates(layercount, tileIndex, tileWidth, tileHeight, this.Width, out sprite.X, out sprite.Y, out sprite.Z); + + sprite.ScaleX = tileWidth / 2.0f; + sprite.ScaleY = tileHeight / 2.0f; + + if (tileSet.Tileoffset != null && tileSet.Tileoffset.Length == 1) + { + sprite.X += tileSet.Tileoffset[0].x; + sprite.Y -= tileSet.Tileoffset[0].y; + } + + + sprite.X *= scale; + sprite.Y *= scale; + // Update August 28, 2012 + // The TMX converter splits + // the Layers by their Z values. + // We want each Layer to have its + // own explicit Z value, so we don't + // want to adjust the Z's when we scale: + //sprite.Z *= scale; + + sprite.ScaleX *= scale; + sprite.ScaleY *= scale; + return sprite; + } + + private static bool IsName(string key) + { + return property.GetStrippedName(key).ToLower() == "name"; + } + + public void CalculateWorldCoordinates(int layerIndex, int tileIndex, int tileWidth, int tileHeight, int layerWidth, out float x, out float y, out float z) + { + int normalizedX = tileIndex % this.Width; + int normalizedY = tileIndex / this.Width; + CalculateWorldCoordinates(layerIndex, normalizedX, normalizedY, tileWidth, tileHeight, layerWidth, out x, out y, out z); + } + + public void CalculateWorldCoordinates(int layerIndex, float normalizedX, float normalizedY, int tileWidth, int tileHeight, int layerWidth, out float x, out float y, out float z) + { + if (this.orientation == null || this.orientation.Equals("orthogonal")) + { + x = (normalizedX * this.tilewidth) + (this.tilewidth / 2.0f); + x += (tileWidth - this.tilewidth) / 2.0f; + y = -(normalizedY * this.tileheight) - (this.tileheight / 2.0f); + y += (tileHeight - this.tileheight) / 2.0f; + z = layerIndex; + } + else if (this.orientation != null && this.orientation.Equals("isometric")) + { + y = -((normalizedX * this.tilewidth / 2.0f) + (normalizedY * this.tilewidth / 2.0f)) / 2; + y += tileHeight / 2.0f; + x = -((normalizedY * this.tilewidth / 2.0f) - (normalizedX * this.tileheight / 2.0f) * 2); + x += tileWidth / 2.0f; + z = ((normalizedY * layerWidth + normalizedX) * .000001f) + layerIndex; + } + else + { + throw new NotImplementedException("Unknown orientation type"); + } + + x += Offset.Item1; + y += Offset.Item2; + z += Offset.Item3; + } + + public static void SetSpriteTextureCoordinates(uint gid, SpriteSave sprite, Tileset tileSet, string orientation) + { + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + + int leftPixelCoord; + int topPixelCoord; + int rightPixelCoord; + int bottomPixelCoord; + GetPixelCoordinatesFromGid(gid, tileSet, + out leftPixelCoord, out topPixelCoord, out rightPixelCoord, out bottomPixelCoord); + + + bool flipDiagonally; + var gidWithoutRotation = gid & 0x0fffffff; + const uint FlippedDiagonallyFlag = 0x20000000; + flipDiagonally = (gid & FlippedDiagonallyFlag) == FlippedDiagonallyFlag; + + + //if (flipDiagonally) + //{ + // // this turns: + // // 1---2 + // // | | + // // 3---4 + + // // into: + // // 1---3 + // // | | + // // 2---4 + + // int newLeft = topPixelCoord; + // int newRight = bottomPixelCoord; + // int newTop = leftPixelCoord; + // int newBottom = rightPixelCoord; + + // topPixelCoord = newTop; + // bottomPixelCoord = newBottom; + // leftPixelCoord = newLeft; + // rightPixelCoord = newRight; + //} + + // Calculate relative texture coordinates based on pixel coordinates + var changeVal = LessOrGreaterDesired.Greater; + + if (orientation == "isometric") + { + changeVal = LessOrGreaterDesired.NoChange; + } + + sprite.TopTextureCoordinate = GetTextureCoordinate(topPixelCoord, imageHeight, changeVal); + sprite.LeftTextureCoordinate = GetTextureCoordinate(leftPixelCoord, imageWidth, changeVal); + + changeVal = LessOrGreaterDesired.Less; + if (orientation == "isometric") + { + changeVal = LessOrGreaterDesired.NoChange; + } + + sprite.RightTextureCoordinate = GetTextureCoordinate(rightPixelCoord, imageWidth, changeVal); + sprite.BottomTextureCoordinate = GetTextureCoordinate(bottomPixelCoord, imageHeight, changeVal); + + if (flipDiagonally) + { + sprite.RotationZ = Microsoft.Xna.Framework.MathHelper.PiOver2; + sprite.FlipHorizontal = true; + } + } + + public static void GetPixelCoordinatesFromGid(uint gid, Tileset tileSet, + out int leftPixelCoord, out int topPixelCoord, out int rightPixelCoord, out int bottomPixelCoord) + { + int imageWidth = tileSet.Images[0].width; + int imageHeight = tileSet.Images[0].height; + int tileWidth = tileSet.Tilewidth; + int spacing = tileSet.Spacing; + int tileHeight = tileSet.Tileheight; + int margin = tileSet.Margin; + + + var gidWithoutRotation = gid & 0x0fffffff; + + const uint FlippedHorizontallyFlag = 0x80000000; + const uint FlippedVerticallyFlag = 0x40000000; + const uint FlippedDiagonallyFlag = 0x20000000; + + bool flipHorizontally = (gid & FlippedHorizontallyFlag) == FlippedHorizontallyFlag; + bool flipVertically = (gid & FlippedVerticallyFlag) == FlippedVerticallyFlag; + bool flipDiagonally = (gid & FlippedDiagonallyFlag) == FlippedDiagonallyFlag; + + // Calculate pixel coordinates in the texture sheet + leftPixelCoord = CalculateXCoordinate(gidWithoutRotation - tileSet.Firstgid, imageWidth, tileWidth, spacing, margin); + topPixelCoord = CalculateYCoordinate(gidWithoutRotation - tileSet.Firstgid, imageWidth, tileWidth, tileHeight, spacing, margin); + rightPixelCoord = leftPixelCoord + tileWidth; + bottomPixelCoord = topPixelCoord + tileHeight; + + if ((flipHorizontally && flipDiagonally == false) || + (flipVertically && flipDiagonally)) + { + var temp = rightPixelCoord; + rightPixelCoord = leftPixelCoord; + leftPixelCoord = temp; + } + + if ((flipVertically && flipDiagonally == false) || + (flipHorizontally && flipDiagonally)) + { + var temp = topPixelCoord; + topPixelCoord = bottomPixelCoord; + bottomPixelCoord = temp; + + } + } + + public Tileset GetTilesetForGid(uint gid, bool shouldRemoveFlipFlags = true) + { + var effectiveGid = gid; + + if (shouldRemoveFlipFlags) + { + effectiveGid = 0x0fffffff & gid; + } + + // Assuming tilesets are sorted by the firstgid value... + // Resort with LINQ if not + if (Tilesets != null) + { + for (int i = Tilesets.Count - 1; i >= 0; --i) + { + Tileset tileSet = Tilesets[i]; + if (effectiveGid >= tileSet.Firstgid) + { + return tileSet; + } + } + } + return null; + } + + private static float GetTextureCoordinate(int pixelCoord, int dimension, LessOrGreaterDesired lessOrGreaterDesired) + { + float asFloat = pixelCoord / (float)dimension; + + //const float modValue = .000001f; + const float modValue = .000002f; + //const float modValue = .00001f; + switch (lessOrGreaterDesired) + { + case LessOrGreaterDesired.Greater: + return asFloat + modValue; + case LessOrGreaterDesired.Less: + return asFloat - modValue; + default: + return asFloat; + } + } + + public static int CalculateYCoordinate(uint gid, int imageWidth, int tileWidth, int tileHeight, int spacing, int margin) + { + + int tilesWide = TilesetExtensionMethods.GetNumberOfTilesWide( + imageWidth, margin, tileWidth, spacing); + + int normalizedy = (int)(gid / tilesWide); + int pixely = normalizedy * (tileHeight + spacing) + margin; + + return pixely; + } + + public static int CalculateXCoordinate(uint gid, int imageWidth, int tileWidth, int spacing, int margin) + { + var tilesWide = TilesetExtensionMethods.GetNumberOfTilesWide( + imageWidth, margin, tileWidth, spacing); + + + int normalizedX = (int)(gid % tilesWide); + int pixelX = normalizedX * (tileWidth + spacing) + margin; + + return pixelX; + } + + public static TiledMapSave FromFile(string fileName) + { + if (FileManager.IsRelative(fileName)) + { + fileName = FileManager.RelativeDirectory + fileName; + } + + // I believe the relative directory of the TMS must be its own directory so that + // image references can be tracked on XML deserialization + string oldRelativeDirectory = FileManager.RelativeDirectory; + try + { + FileManager.RelativeDirectory = FileManager.GetDirectory(fileName); + } + catch + { + } + TiledMapSave tms = null; + + try + { + tms = FileManager.XmlDeserialize(fileName); + tms.FileName = fileName; + } + finally + { + FileManager.RelativeDirectory = oldRelativeDirectory; + + } + + + + + foreach (MapLayer layer in tms.Layers) + { + if (!layer.PropertyDictionary.ContainsKey("VisibleBehavior")) + { + layer.VisibleBehavior = LayerVisibleBehaviorValue; + } + else + { + if (!Enum.TryParse(layer.PropertyDictionary["VisibleBehavior"], out layer.VisibleBehavior)) + { + layer.VisibleBehavior = LayerVisibleBehaviorValue; + } + } + } + return tms; + } + + public void Save(string fileName) + { + FileManager.XmlSerialize(this, fileName); + + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Serialization.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Serialization.cs new file mode 100644 index 000000000..69f3aa0e2 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapSave.Serialization.cs @@ -0,0 +1,993 @@ +using System.Xml.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Linq; +using System.Xml.Schema; + +// +// This source code was auto-generated by xsd, Version=2.0.50727.3038. +// +namespace TMXGlueLib +{ + #region TiledMapSave Class + /// + [XmlType(AnonymousType = true)] + [XmlRoot(ElementName = "map", Namespace = "", IsNullable = false)] + public partial class TiledMapSave + { + + public enum LayerVisibleBehavior { Ignore, Match, Skip }; + + #region Fields + + private IDictionary propertyDictionaryField = null; + + List mProperties = new List(); + #endregion + + [XmlIgnore] + public string FileName + { + get; + set; + } + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", "map"); + } + return propertyDictionaryField; + } + } + } + + public static IDictionary BuildPropertyDictionaryConcurrently(IEnumerable properties) + { + ConcurrentDictionary propertyDictionary = new ConcurrentDictionary(); + Parallel.ForEach(properties, (p) => + { + if (p != null && !propertyDictionary.ContainsKey(p.name)) + { + // Don't ToLower it - it causes problems when we try to get the column name out again. + //propertyDictionaryField.Add(p.name.ToLower(), p.value); + + propertyDictionary[p.name] = p.value; + } + }); + return propertyDictionary; + } + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + public bool ShouldSerializeproperties() + { + return mProperties != null && mProperties.Count != 0; + } + + + /// + [XmlElement("tileset", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public List Tilesets + { + get; + set; + } + + + [XmlElement("layer", typeof(MapLayer))] + [XmlElement("imagelayer", typeof(MapImageLayer))] + [XmlElement("objectgroup", typeof(mapObjectgroup))] + public List MapLayers { get; set; } + + /// + + + [XmlIgnore] + public List Layers => MapLayers.OfType().ToList(); + + /// + [XmlIgnore] + public List objectgroup => MapLayers.OfType().ToList(); + + [XmlIgnore] + public List ImageLayers => MapLayers.OfType().ToList(); + + /// + [XmlAttribute()] + public string version + { + get; + set; + } + + /// + [XmlAttribute()] + public string orientation + { + get; + set; + } + + /// + /// The number of cells this map has on the X axis. + /// + [XmlAttribute("width")] + public int Width + { + get; + set; + } + + /// + /// The number of cells this map has on the Y axis. + /// + [XmlAttribute("height")] + public int Height + { + get; + set; + } + + /// + [XmlAttribute()] + public int tilewidth + { + get; + set; + } + + /// + [XmlAttribute()] + public int tileheight + { + get; + set; + } + + public TiledMapSave() + { + MapLayers = new List(); + Tilesets = new List(); + } + + public List GetReferencedFiles() + { + List referencedFiles = new List(); + + if(this.Tilesets != null) + { + foreach (var tileset in this.Tilesets) + { + if(!string.IsNullOrEmpty(tileset.Source )) + { + referencedFiles.Add(tileset.Source); + } + else if (tileset?.Images != null && tileset.Images.Length != 0) + { + var image = tileset.Images[0]; + + string fileName = image.Source; + + // keep it relative + referencedFiles.Add(fileName); + + } + + } + } + + + return referencedFiles; + } + } + + #endregion + + #region property Class + public partial class property + { + [XmlAttribute()] + public string name + { + get; + set; + } + + [XmlAttribute()] + public string value + { + get; + set; + } + + [XmlAttribute("type")] + public string Type { get; set; } + + [XmlIgnore] + public string StrippedName + { + get + { + return GetStrippedName(this.name); + } + } + + public string StrippedNameLower + { + get + { + return GetStrippedName(this.name).ToLowerInvariant(); + } + } + + + public static string GetStrippedName(string name) + { + string nameWithoutType; + if (name.Contains('(') && name.Contains(')')) + { + int open = name.IndexOf('('); + int close = name.IndexOf(')'); + + nameWithoutType = name.Substring(0, open).Trim(); + } + else + { + nameWithoutType = name; + } + + return nameWithoutType; + } + + public static string GetPropertyName(string name) + { + if (name.Contains('(') && name.Contains(')')) + { + int open = name.IndexOf('('); + int close = name.IndexOf(')'); + + int afterOpen = open+1; + + return name.Substring(afterOpen, close - afterOpen); + + } + else + { + return null; + } + + } + + + + public override string ToString() + { + return name + " = " + value; + } + } + #endregion + +#if !UWP + [Serializable] +#endif + public partial class MapImageLayer : AbstractMapLayer + { + private MapImageLayerImage imageField; + + private float _offsetX; + private float _offsetY; + private float _opacity; + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + private IDictionary propertyDictionaryField = null; + private int _visibleAsInt = 1; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("image", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public MapImageLayerImage ImageObject + { + get + { + return this.imageField; + } + set + { + this.imageField = value; + } + } + + + [XmlAttribute("visible")] + public int VisibleAsInt + { + get { return _visibleAsInt; } + set { _visibleAsInt = value; } + } + + + [XmlAttribute("opacity")] + public float Opacity + { + get { return _opacity; } + set { _opacity = value; } + } + + [XmlAttribute("offsetx")] + public float OffsetX + { + get { return _offsetX; } + set { _offsetX = value; } + } + + [XmlAttribute("offsety")] + public float OffsetY + { + get { return _offsetY; } + set { _offsetY = value; } + } + + [XmlIgnore] + public bool Visible + { + get + { + return VisibleAsInt != 0; + } + } + } + + public partial class MapImageLayerImage + { + private string _source; + + [XmlAttributeAttribute(AttributeName = "source")] + public string Source + { + get { return _source; } + set { _source = value; } + } + + /// + [XmlAttributeAttribute(AttributeName = "width")] + public float Width { get; set; } + + /// + [XmlAttributeAttribute(AttributeName = "height")] + public float Height { get; set; } + } + + + /// + [XmlType(AnonymousType = true)] + [XmlRoot(ElementName = "mapTilesetImage", Namespace = "", IsNullable = false)] + public partial class TilesetImage + { + + private string sourceField; + + /// + [XmlAttribute("source")] + public string Source + { + get + { + return this.sourceField; + } + set + { + this.sourceField = value; + if (this.sourceField != null) + { + this.sourceField = this.sourceField.Replace("/", "\\"); + } + } + } + + [XmlIgnore] + public string sourceFileName + { + get + { + if (!string.IsNullOrEmpty(Source) && Source.Contains("\\")) + { + return Source.Substring(Source.LastIndexOf('\\') + 1); + } + else + { + return Source; + } + } + } + + [XmlIgnore] + public string sourceDirectory + { + get + { + if (!string.IsNullOrEmpty(Source) && Source.Contains("\\")) + { + return Source.Substring(0, Source.LastIndexOf('\\')); + } + else + { + return Source; + } + } + } + + + /// + [XmlAttribute()] + public int width + { + get; + set; + } + + /// + [XmlAttribute()] + public int height + { + get; + set; + } + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapTilesetTileOffset + { + + private int xField; + + private int yField; + + /// + [XmlAttribute()] + public int x + { + get + { + return xField; + } + set + { + xField = value; + } + } + + /// + [XmlAttribute()] + public int y + { + get + { + return yField; + } + set + { + yField = value; + } + } + } + + + + public partial class mapLayerDataTile + { + [XmlAttribute()] + public string gid { get; set; } + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapLayerData + { + + private string encodingField; + + private string compressionField; + + private string valueField; + + /// + [XmlAttribute()] + public string encoding + { + get + { + return this.encodingField; + } + set + { + this.encodingField = value; + } + } + + /// + [XmlAttribute()] + public string compression + { + get + { + return this.compressionField; + } + set + { + this.compressionField = value; + } + } + + [XmlElement("tile", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapLayerDataTile[] dataTiles { get; set; } + + /// + [XmlText()] + public string Value + { + get + { + return this.valueField; + } + set + { + this.valueField = value; + } + } + + + /// + /// Represents the index that this tile is displaying from the source tile map. This is 1-based. 0 means no tile. + /// This can span multiple tilesets. + /// + private List _ids = null; + + + /// + /// Represents all of the tiles in this layer. A tile with index 0 means there is no tile there. Non-zero values + /// mean that the value is painted. Painted values are global IDs of tiles. Index 0 is the top-left tile. Increasing + /// the index moves towards the right. When reading the end of the row, the next index represents the first tile in the + /// next row. + /// + [XmlIgnore] + public List tiles + { + get + { + if (encodingField != "base64" && encodingField != null && encodingField != "csv") + { + throw new NotImplementedException("Unknown encoding: " + encodingField); + } + + if (_ids == null) + { + if (encodingField != null && encodingField != "csv") + { + _ids = new List(length); + // get a stream to the decoded Base64 text + + var trimmedValue = Value.Trim(); + + Stream data = new MemoryStream(Convert.FromBase64String(trimmedValue), false); + switch (compression) + { + case "gzip": + data = new GZipStream(data, CompressionMode.Decompress, false); + break; + case "zlib": +#if SUPPORTS_ZLIB + data = new Ionic.Zlib.ZlibStream(data, Ionic.Zlib.CompressionMode.Decompress, false); +#else + throw new NotImplementedException("Does not support zlib"); +#endif + break; + case null: + // Not compressed. Data is already decoded. + break; + default: + throw new InvalidOperationException("Unknown compression: " + compression); + } + + // simply read in all the integers + using (data) + { + using (BinaryReader reader = new BinaryReader(data)) + { + _ids = new List(); + for (int i = 0; i < length; i++) + { + _ids.Add(reader.ReadUInt32()); + } + } + } + } + else if (encodingField == "csv") + { + string[] idStrs = Value.Split(",\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + _ids = idStrs.AsParallel().Select(id => + { + uint gid; + if (!uint.TryParse(id, out gid)) + { + gid = 0; + } + return gid; + }).ToList(); + } + else if (encodingField == null) + { + _ids = dataTiles.AsParallel().Select(dt => + { + uint gid; + if (!uint.TryParse(dt.gid, out gid)) + { + gid = 0; + } + return gid; + }).ToList(); + } + } + + return _ids; + } + + } + + public int length { get; set; } + } + +#if !UWP + [Serializable] +#endif + public partial class mapObjectgroup : AbstractMapLayer + { + private mapObjectgroupObject[] objectField; + + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + private IDictionary propertyDictionaryField = null; + private int _visibleAsInt = 1; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + return propertyDictionaryField; + } + } + } + + /// + [XmlElement("object", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObject[] @object + { + get + { + return this.objectField; + } + set + { + this.objectField = value; + } + } + + + [XmlAttribute("visible")] + public int VisibleAsInt + { + get { return _visibleAsInt; } + set { _visibleAsInt = value; } + } + + [XmlIgnore] + public bool Visible + { + get + { + return VisibleAsInt != 0; + } + } + + public override string ToString() + { + return Name; + } + } + + /// + public partial class mapObjectgroupObject + { + private mapObjectgroupObjectEllipse ellipseField = null; + + private mapObjectgroupObjectPolygon[] polygonField; + + private mapObjectgroupObjectPolyline[] polylineField; + + private double xField; + + private double yField; + + private string _name; + + + [XmlAttributeAttribute(AttributeName = "name")] + public string Name + { + get { return _name; } + set { _name = value; } + } + + [XmlAttribute(AttributeName = "gid")] + public string __proxygid + { + get { return gid?.ToString(); } + set { gid = uint.Parse(value); } + } + + [XmlIgnore] + public uint? gid { get; set; } + + private IDictionary propertyDictionaryField = null; + + [XmlIgnore] + public IDictionary PropertyDictionary + { + get + { + lock (this) + { + if (propertyDictionaryField == null) + { + propertyDictionaryField = TiledMapSave.BuildPropertyDictionaryConcurrently(properties); + } + if (!string.IsNullOrEmpty(this.Name) && !propertyDictionaryField.Any(p => p.Key.Equals("name", StringComparison.OrdinalIgnoreCase))) + { + propertyDictionaryField.Add("name", this.Name); + } + return propertyDictionaryField; + } + } + } + + List mProperties = new List(); + + public List properties + { + get { return mProperties; } + set + { + mProperties = value; + } + } + + [XmlElement("ellipse", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectEllipse ellipse + { + get + { + return ellipseField; + } + set + { + ellipseField = value; + } + } + + /// + [XmlElement("polygon", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectPolygon[] polygon + { + get + { + return this.polygonField; + } + set + { + this.polygonField = value; + } + } + + /// + [XmlElement("polyline", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public mapObjectgroupObjectPolyline[] polyline + { + get + { + return this.polylineField; + } + set + { + this.polylineField = value; + } + } + + /// + [XmlAttribute("x")] + public double x + { + get + { + return this.xField; + } + set + { + this.xField = value; + } + } + + /// + [XmlAttribute("y")] + public double y + { + get + { + return this.yField; + } + set + { + this.yField = value; + } + } + + /// + [XmlAttribute()] + public float width { get; set; } + + /// + [XmlAttribute()] + public float height { get; set; } + + [XmlAttribute("rotation")] + public double Rotation + { + get; + set; + } + } + + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectEllipse + { + + } + + /// + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectPolygon + { + + private string pointsField; + + /// + [XmlAttribute()] + public string points + { + get + { + return this.pointsField; + } + set + { + this.pointsField = value; + } + } + } + +#region mapObjectgroupObjectPolyline + + /// + [XmlType(AnonymousType = true)] + public partial class mapObjectgroupObjectPolyline + { + + private string pointsField; + + /// + [XmlAttribute()] + public string points + { + get + { + return this.pointsField; + } + set + { + this.pointsField = value; + } + } + } + +#endregion + +#region NewDataSet Class + /// + [XmlType(AnonymousType = true)] + [XmlRoot(Namespace = "", IsNullable = false)] + public partial class NewDataSet + { + + private TiledMapSave[] itemsField; + + /// + [XmlElement("map")] + public TiledMapSave[] Items + { + get + { + return this.itemsField; + } + set + { + this.itemsField = value; + } + } + } + +#endregion + +} \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapToShapeCollectionConverter.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapToShapeCollectionConverter.cs new file mode 100644 index 000000000..579078160 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TiledMapToShapeCollectionConverter.cs @@ -0,0 +1,268 @@ +using FlatRedBall.Content.Math.Geometry; +using FlatRedBall.Content.Polygon; +using FlatRedBall.Math.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Point = FlatRedBall.Math.Geometry.Point; + +namespace TMXGlueLib +{ + public static class TiledMapToShapeCollectionConverter + { + public static ShapeCollection ToShapeCollection(this TiledMapSave tiledMapSave, string layerName) + { + MapLayer mapLayer = null; + if (!string.IsNullOrEmpty(layerName)) + { + mapLayer = tiledMapSave.Layers.FirstOrDefault(l => l.Name.Equals(layerName)); + } + var shapes = new ShapeCollection(); + + if ((mapLayer != null && !mapLayer.IsVisible && mapLayer.VisibleBehavior == TMXGlueLib.TiledMapSave.LayerVisibleBehavior.Skip) || + tiledMapSave.objectgroup == null || tiledMapSave.objectgroup.Count == 0) + { + return shapes; + } + + foreach (mapObjectgroup group in tiledMapSave.objectgroup) + { + if (group.@object != null && !string.IsNullOrEmpty(group.Name) && (string.IsNullOrEmpty(layerName) || group.Name.Equals(layerName))) + { + foreach (mapObjectgroupObject @object in group.@object) + { + AddShapeToShapeCollection(@object, shapes); + } + + } + } + return shapes; + } + + public static void AddShapeToShapeCollection(mapObjectgroupObject @object, ShapeCollection shapes) + { + + //////////////////////////Early out//////////////////////////////// + ///November 8th, 2015 + ///Jesse Crafts-Finch + ///If a polygon has a gid, and therefore an image associate with it, it will be turned into a spritesave, not a polygon. + if (@object.gid != null) + { + return; + } + ////////////////////////End Early Out///////////////////////////////// + Polygon polygon; + AxisAlignedRectangle rectangle; + Circle circle; + + ConvertTiledObjectToFrbShape(@object, out polygon, out rectangle, out circle); + + if (polygon != null) + { + shapes.Polygons.Add(polygon); + } + if (rectangle != null) + { + shapes.AxisAlignedRectangles.Add(rectangle); + } + if (circle != null) + { + shapes.Circles.Add(circle); + } + } + + public static void ConvertTiledObjectToFrbShape(mapObjectgroupObject @object, out Polygon polygon, out AxisAlignedRectangle rectangle, out Circle circle) + { + polygon = null; + rectangle = null; + circle = null; + if (@object.polygon != null) + { + foreach (mapObjectgroupObjectPolygon tiledPolygon in @object.polygon) + { + // TODO: Make this a rectangle object + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, + @object.x, @object.y, @object.Rotation, tiledPolygon.points, true); + } + } + + if (@object.polyline != null) + { + foreach (mapObjectgroupObjectPolyline polyline in @object.polyline) + { + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, + @object.x, @object.y, @object.Rotation, polyline.points, false); + } + } + + if (@object.polygon == null && @object.polyline == null) + { + if (@object.Rotation == 0 && @object.ellipse == null) + { + rectangle = new AxisAlignedRectangle() + { + Name = @object.Name, + X = (float)@object.x + (@object.width / 2), + Y = (float)-@object.y - (@object.height / 2), + ScaleX = @object.width / 2, + ScaleY = @object.height / 2, + }; + + } + else if (@object.ellipse != null && @object.width == @object.height) + { + circle = new Circle() + { + Name = @object.Name, + X = (float)@object.x + (@object.width / 2), + Y = (float)-@object.y - (@object.height / 2), + Radius = @object.width / 2 + }; + + } + else + { + polygon = ConvertTmxObjectToFrbPolygon(@object.Name, @object.x, @object.y, @object.width, @object.height, @object.Rotation, @object.ellipse); + } + } + } + + private static Polygon ConvertTmxObjectToFrbPolygon(string name, + double x, double y, double w, double h, double rotation, mapObjectgroupObjectEllipse ellipse) + { + var pointsSb = new StringBuilder(); + + if (ellipse == null) + { + pointsSb.AppendFormat("{0},{1}", -w / 2, -h / 2); + + pointsSb.AppendFormat(" {0},{1}", w / 2, -h / 2); + pointsSb.AppendFormat(" {0},{1}", w / 2, h / 2); + pointsSb.AppendFormat(" {0},{1}", -w / 2, h / 2); + } + else + { + const double a = .5; + const double b = .5; + + // x = a cos t + // y = b cos t + var first = true; + string firstPoint = ""; + for (var angle = 0; angle <= 360; angle += 18) + { + var radians = MathHelper.ToRadians(angle); + + + // This code made the position of the poly be top left, not optimized! + //var newx = a*Math.Cos(radians)*w+w/2; + //var newy = b*Math.Sin(radians)*h+h/2; + + var newx = a * Math.Cos(radians) * w; + var newy = b * Math.Sin(radians) * h; + + if (first) + { + firstPoint = string.Format("{0},{1}", newx, newy); + } + pointsSb.AppendFormat("{2}{0},{1}", newx, newy, first ? "" : " "); + first = false; + } + + pointsSb.AppendFormat(" {0}", firstPoint); + } + + return ConvertTmxObjectToFrbPolygon(name, x + w / 2.0f, y + h / 2.0f, rotation, pointsSb.ToString(), true); + } + + private static Polygon ConvertTmxObjectToFrbPolygon(string name, + double x, double y, double rotation, string points, bool connectBackToStart) + { + if (string.IsNullOrEmpty(points)) + { + return null; + } + + var polygon = new Polygon(); + string[] pointString = points.Split(" ".ToCharArray()); + + polygon.Name = name; + + // Nov. 19th, 2014 - Domenic: + // I am ripping this code apart a little, because shapes really should not involve tile sizes in their x/y calculations. + // I'm not sure why this was ever done this way, as TMX gives the X/Y and width/height already. The old way was basically to convert + // the x/y coordinates into tile based coordinates and then re-convert back to full x/y coordinates. This makes no sense any more to me. + // + // Having examined TMX format a little more, it seems that the x/y position is always specified + // + //float fx = x; + //float fy = y; + + //if ("orthogonal".Equals(tiledMapSave.orientation)) + //{ + // fx -= tiledMapSave.tilewidth / 2.0f; + // fy -= tiledMapSave.tileheight + (tiledMapSave.tileheight / 2.0f); + //} + //else if ("isometric".Equals(tiledMapSave.orientation)) + //{ + // fx -= tiledMapSave.tilewidth / 4.0f; + // fy -= tiledMapSave.tileheight / 2.0f; + //} + + //tiledMapSave.CalculateWorldCoordinates( + // 0, fx / tiledMapSave.tileheight, fy / tiledMapSave.tileheight, + // tiledMapSave.tilewidth, tiledMapSave.tileheight, + // w * tiledMapSave.tilewidth, out newx, out newy, out z); + + //polygon.X = newx - tiledMapSave.tilewidth / 2.0f; + //polygon.Y = newy - tiledMapSave.tileheight / 2.0f; + //var pointsArr = new Point[pointString.Length + (connectBackToStart ? 1 : 0)]; + + var pointsList = + pointString.Select(p => + { + var xy = p.Split(",".ToCharArray()); + return new Point + { + X = Convert.ToDouble(xy[0], System.Globalization.NumberFormatInfo.InvariantInfo), + Y = -Convert.ToDouble(xy[1], System.Globalization.NumberFormatInfo.InvariantInfo) + }; + }).ToList(); + + if (connectBackToStart) + { + pointsList.Add(new Point(pointsList[0].X, pointsList[0].Y)); + } + + if (IsClockwise(pointsList) == false) + { + pointsList.Reverse(); + } + + polygon.Points = pointsList.ToArray(); + polygon.X = (float)x; + polygon.Y = (float)-y; + polygon.RotationZ = -MathHelper.ToRadians((float)rotation); + + return polygon; + } + + // From: + // https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order + private static bool IsClockwise(List pointsList) + { + double sum = 0; + for (int i = 0; i < pointsList.Count - 1; i++) + { + var point = pointsList[i]; + var pointAfter = pointsList[i + 1]; + + sum += (pointAfter.X - point.X) * (pointAfter.Y + point.Y); + } + + return sum > 0; + } + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/Tileset.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/Tileset.cs new file mode 100644 index 000000000..cd5ab9210 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/Tileset.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FlatRedBall.TileGraphics +{ + public class Tileset + { + private Texture2D mTexture; + private int mNumRows, mNumCols; + private float mTextureTileHeight, mTextureTileWidth; + private int mTileDimensionWidth, mTileDimensionHeight; // in pixels + + + + public Tileset(string pTilesetFilename, int pTileDimensionWidth, int pTileDimensionHeight) + { + mTexture = FlatRedBall.FlatRedBallServices.Load(pTilesetFilename); + + mTileDimensionWidth = pTileDimensionWidth; + mTileDimensionHeight = pTileDimensionHeight; + + mNumCols = mTexture.Width / pTileDimensionWidth; + mNumRows = mTexture.Height / pTileDimensionHeight; + + mTextureTileWidth = (float)(pTileDimensionWidth) / (float)(mTexture.Width); + mTextureTileHeight = (float)(pTileDimensionHeight) / (float)(mTexture.Height); + // FlatRedBall.Debugging.Debugger.CommandLineWrite("Dimensions: " + mTextureTileWidth + " x " + mTextureTileHeight); + } + + public Tileset(Texture2D tilesetTexture, int tileDimensionWidth, int tileDimensionHeight) + { + mTexture = tilesetTexture; + + mTileDimensionWidth = tileDimensionWidth; + mTileDimensionHeight = tileDimensionHeight; + + mNumCols = mTexture.Width / tileDimensionWidth; + mNumRows = mTexture.Height / tileDimensionHeight; + + mTextureTileWidth = (float)(tileDimensionWidth) / (float)(mTexture.Width); + mTextureTileHeight = (float)(tileDimensionHeight) / (float)(mTexture.Height); + + + } + + public short GetTextureIndexFromCoordinate(Vector2 topLeftUVCoordinate) + { + short column = (short)(topLeftUVCoordinate.X / mTextureTileWidth); + short row = (short)(topLeftUVCoordinate.Y / mTextureTileHeight); + + return (short)((mNumCols*row) + column); + } + + public Vector2[] GetTextureCoordinateVectorsOfTextureIndex(int textureId) + { + // TODO: pId needs to be constrained! + + Vector2[] coords = new Vector2[4]; + + GetTextureCoordinateVectorsOfTextureIndex(textureId, coords); + + return coords; + + } + + public void GetTextureCoordinateVectorsOfTextureIndex(int textureId, Vector2[] coords) + { + // TODO: pId needs to be constrained! + + float x, y; + if (textureId == 0) + { + x = 0; + y = 0; + } + else + { + x = (float)(textureId % mNumCols) * mTextureTileWidth; + + y = ((int)((float)textureId - x) / mNumCols) * mTextureTileHeight; + } + + + // Coords are + // 3 2 + // + // 0 1 + + GetCoordinatesForTile(coords, x, y); + } + + public void GetCoordinatesForTile(Vector2[] coords, float leftTextureCoordinate, float topTextureCoordinate) + { + + coords[0] = new Vector2(leftTextureCoordinate, topTextureCoordinate + mTextureTileHeight); + coords[1] = new Vector2(leftTextureCoordinate + mTextureTileWidth, topTextureCoordinate + mTextureTileHeight); + coords[2] = new Vector2(leftTextureCoordinate + mTextureTileWidth, topTextureCoordinate); + coords[3] = new Vector2(leftTextureCoordinate, topTextureCoordinate); + } + + + + } +} diff --git a/FRBDK/GlueView2Test/GlueView2/TileGraphics/TilesetExtensionMethods.cs b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TilesetExtensionMethods.cs new file mode 100644 index 000000000..66cdfccab --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/TileGraphics/TilesetExtensionMethods.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TMXGlueLib +{ + public static class TilesetExtensionMethods + { + public static void IndexToCoordinate(this Tileset tileset, long xIndex, long yIndex, out int xCoordinate, out int yCoordinate) + { + xCoordinate = tileset.Margin + (int)xIndex * (tileset.Tilewidth + tileset.Spacing); + yCoordinate = tileset.Margin + (int)yIndex * (tileset.Tileheight + tileset.Spacing); + + + } + + public static void CoordinateToIndex(this Tileset tileset, int xCoordinate, int yCoordinate, out int xIndex, out int yIndex) + { + xIndex = 0; + yIndex = 0; + if (tileset.Images.Length != 0) + { + // We're assuming the first image, not sure why we'd have multiple images in one tileset....or at least we won't + // supportthat yet. + var image = tileset.Images[0]; + + int effectiveImageWidth = tileset.Images[0].width; + int effectiveImageHeight = tileset.Images[0].height; + + if (xCoordinate < effectiveImageWidth && yCoordinate < effectiveImageHeight) + { + + + if (tileset.Margin != 0) + { + xCoordinate -= tileset.Margin * 2; + yCoordinate -= tileset.Margin * 2; + } + + + int effectiveTileWidth = tileset.Tilewidth + tileset.Spacing; + int effectiveTileHeight = tileset.Tileheight + tileset.Spacing; + + xIndex = xCoordinate / effectiveTileWidth; + yIndex = yCoordinate / effectiveTileHeight; + + } + + } + } + + public static int GetNumberOfTilesWide(this Tileset tileset) + { + if (tileset.Images.Length == 0 || tileset.Tilewidth == 0) + { + return 0; + } + else + { + // This is the width of the image as reported by the .tsx or .tmx, but + // it may not actually be the image's width if the .png has changed since + // the tsx/tmx was saved + int imageWidth = tileset.Images[0].width; + + return GetNumberOfTilesWide( + tileset.Images[0].width, tileset.Margin, tileset.Tilewidth, tileset.Spacing); + } + } + + + public static int GetNumberOfTilesWide(int imageWidth, int margin, int tileWidth, int spacing) + { + + if (tileWidth == 0) + { + throw new Exception("The tileWidth must not be 0"); + } + + + // The following logic + // deserves an explanation: + // Consider a simple tileset + // with 2 tiles, and a single + // spacing between them. Assume + // that the tiles are 16 pixels wide + // and that the spacing is 1 pixel wide. + // In this case, the width of the entire + // tileset image would be 33. That's (16+1+16). + // However, let's look at the following line of code + // below: + //int tilesWide = (imageWidth - margin) / (tileWidth + spacing); + // In this case, the formula would be: + // int tilesWide = (33-0) / (16+1); + // Which is equivalent to: + // int tilesWide = 33 / 17; + // which is equivalent to: + // int tilesWide = 1; + // But we clearly stated above that we have two tiles (16+1+16). + // The reason for this is because each tile *except the last* will + // be considered to be wider by 1 because of its spacing value. The + // last one won't be considered wider because there's always one-less space + // than there are tiles. + // We can correct this simply by adding 1 to the resulting tilesWide *if* there + // is spacing... + // Update January 26, 2014 + // We need to multiply margin + // by 2 since the margin applies + // to all sides + // ...so let's do that here: + //int tilesWide = (imageWidth - (2 * margin)) / (tileWidth + spacing); + + //if (spacing != 0) + //{ + // tilesWide++; + //} + + // Update February 7, 2014 + // No, this doesn't seem like it's working right, and it's confusing, so I'm going + // to break this down a bit: + // First let's take off the margin so we can see how + // much usable space we have: + int usableSpace = imageWidth - 2 * margin; + // If there is a margin, all tiles except the last will have the margin added to the right of them + // Since the last one won't have the margin added, let's just add the margin to the usable space, then do + // a simple int division: + usableSpace += spacing; + + return usableSpace / (tileWidth + spacing); + + } + + public static int GetTileCount(this Tileset tileset) + { + int width = tileset.GetNumberOfTilesWide(); + int height = tileset.GetNumberOfTilesTall(); + + return width * height; + + + } + + public static int GetNumberOfTilesTall(this Tileset tileset) + { + if (tileset.Images.Length == 0) + { + return 0; + } + else + { + return GetNumberOfTilesWide( + tileset.Images[0].height, tileset.Margin, tileset.Tileheight, tileset.Spacing); + } + } + + + public static int GetNumberOfTilesTall(int imageHeight, int margin, int tileHeight, int spacing) + { + // See GetNumberOfTilesWide for a discussion about this approach + int usableSpace = imageHeight - 2 * margin; + usableSpace += spacing; + + return usableSpace / (tileHeight + spacing); + + + } + + public static int IndexToLocalId(this Tileset tileset, int xIndex, int yIndex) + { + return xIndex + yIndex * tileset.GetNumberOfTilesWide(); + + } + + public static int CoordinateToLocalId(this Tileset tileset, int xCoordinate, int yCoordinate) + { + int xIndex; + int yIndex; + + tileset.CoordinateToIndex(xCoordinate, yCoordinate, out xIndex, out yIndex); + + return tileset.IndexToLocalId(xIndex, yIndex); + + } + + } +} +; \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/app.config b/FRBDK/GlueView2Test/GlueView2/app.config new file mode 100644 index 000000000..4c04e543d --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/app.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/FRBDK/GlueView2Test/GlueView2/glueview2.glux b/FRBDK/GlueView2Test/GlueView2/glueview2.glux new file mode 100644 index 000000000..fed6c3965 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/glueview2.glux @@ -0,0 +1,81 @@ + + + true + false + false + 800 + 600 + false + 800 + 600 + + + + GLUE + + GLUE + Screens\MainScreen + + + + + + false + false + + + Screens\MainScreen + + + false + false + + + + + + + + bool + HasCollision + + + string + EntityToCreate + + + string + Name + + + System.Collections.Generic.List<FlatRedBall.Content.AnimationChain.AnimationFrameSaveBase> + EmbeddedAnimation + + + TileMapInfo + true + + + + true + ../../ + + + + + true + true + 800 + 600 + false + 16 + 9 + true + false + false + false + 100 + StretchVisibleArea + Height + + \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/libSDL2-2.0.0.dylib b/FRBDK/GlueView2Test/GlueView2/libSDL2-2.0.0.dylib new file mode 100644 index 000000000..11f29935e Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/libSDL2-2.0.0.dylib differ diff --git a/FRBDK/GlueView2Test/GlueView2/libopenal.1.dylib b/FRBDK/GlueView2Test/GlueView2/libopenal.1.dylib new file mode 100644 index 000000000..df9570d57 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/libopenal.1.dylib differ diff --git a/FRBDK/GlueView2Test/GlueView2/packages.config b/FRBDK/GlueView2Test/GlueView2/packages.config new file mode 100644 index 000000000..63dbd9359 --- /dev/null +++ b/FRBDK/GlueView2Test/GlueView2/packages.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FRBDK/GlueView2Test/GlueView2/x64/SDL2.dll b/FRBDK/GlueView2Test/GlueView2/x64/SDL2.dll new file mode 100644 index 000000000..8e0c64cbc Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x64/SDL2.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/x64/libSDL2-2.0.so.0 b/FRBDK/GlueView2Test/GlueView2/x64/libSDL2-2.0.so.0 new file mode 100644 index 000000000..de244bc59 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x64/libSDL2-2.0.so.0 differ diff --git a/FRBDK/GlueView2Test/GlueView2/x64/libopenal.so.1 b/FRBDK/GlueView2Test/GlueView2/x64/libopenal.so.1 new file mode 100644 index 000000000..af45fd0c2 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x64/libopenal.so.1 differ diff --git a/FRBDK/GlueView2Test/GlueView2/x64/soft_oal.dll b/FRBDK/GlueView2Test/GlueView2/x64/soft_oal.dll new file mode 100644 index 000000000..e24538a5f Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x64/soft_oal.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/x86/SDL2.dll b/FRBDK/GlueView2Test/GlueView2/x86/SDL2.dll new file mode 100644 index 000000000..2b7e3193c Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x86/SDL2.dll differ diff --git a/FRBDK/GlueView2Test/GlueView2/x86/libSDL2-2.0.so.0 b/FRBDK/GlueView2Test/GlueView2/x86/libSDL2-2.0.so.0 new file mode 100644 index 000000000..01aa1979f Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x86/libSDL2-2.0.so.0 differ diff --git a/FRBDK/GlueView2Test/GlueView2/x86/libopenal.so.1 b/FRBDK/GlueView2Test/GlueView2/x86/libopenal.so.1 new file mode 100644 index 000000000..97a503b02 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x86/libopenal.so.1 differ diff --git a/FRBDK/GlueView2Test/GlueView2/x86/soft_oal.dll b/FRBDK/GlueView2Test/GlueView2/x86/soft_oal.dll new file mode 100644 index 000000000..1e3bddd56 Binary files /dev/null and b/FRBDK/GlueView2Test/GlueView2/x86/soft_oal.dll differ