Skip to content

Instantly share code, notes, and snippets.

@wtrebella
Last active December 16, 2015 13:48
Show Gist options
  • Save wtrebella/5444072 to your computer and use it in GitHub Desktop.
Save wtrebella/5444072 to your computer and use it in GitHub Desktop.
This is a collection of classes that will allow you to simply make an array of vertices and have them drawn on screen as a polygon (with a solid color). You will also be able to collide a circle with that polygon, or just get the vertex points based on its rotation, scale, and position.
using UnityEngine;
using System.Collections;
// This is just an example of how to use the polygon sprite
public class WTDrawingPolygonsScene : MonoBehaviour {
void Start () {
FutileParams fp = new FutileParams(true, true, false, false);
fp.AddResolutionLevel(480f, 1.0f, 1.0f, "-res1");
fp.backgroundColor = Color.black;
fp.origin = Vector2.zero;
Futile.instance.Init(fp);
Vector2[] vertices = new Vector2[] {
new Vector2(0, 0),
new Vector2(25, 5),
new Vector2(35, 45),
new Vector2(20, 23),
new Vector2(5, 57)
};
WTPolygonSprite s = new WTPolygonSprite(new WTPolygonData(vertices));
s.color = Color.green;
s.x = Futile.screen.halfWidth - 130;
s.y = Futile.screen.halfHeight;
Futile.stage.AddChild(s);
s = new WTPolygonSprite(new WTPolygonData(vertices));
s.color = Color.green;
s.x = Futile.screen.halfWidth - 20;
s.y = Futile.screen.halfHeight;
s.scaleX = 2.0f;
s.scaleY = 0.5f;
s.rotation = 143;
Futile.stage.AddChild(s);
// This will log the original values of the vertices
Vector2[] originalVertices = s.polygonData.polygonVertices;
for (int i = 0; i < originalVertices.Length; i++) {
Vector2 v = originalVertices[i];
Debug.Log("Original vertex " + i + ": " + v);
}
Debug.Log("===================================================================================================");
// This will log the values of the vertices after taking rotation, scale, and position into account
Vector2[] adjustedVertices = s.GetAdjustedPolygonData().polygonVertices;
for (int i = 0; i < adjustedVertices.Length; i++) {
Vector2 v = adjustedVertices[i];
Debug.Log("Adjusted vertex " + i + ": " + v);
}
int circleResolution = 50;
float radius = 50;
Vector2[] circleVertices = new Vector2[circleResolution];
for (int i = 0; i < circleResolution; i++) {
float x = Mathf.Cos(2 * Mathf.PI / circleResolution * (i + 1)) * radius;
float y = Mathf.Sin(2 * Mathf.PI / circleResolution * (i + 1)) * radius;
circleVertices[i] = new Vector2(x, y);
}
WTPolygonSprite sCircle = new WTPolygonSprite(new WTPolygonData(circleVertices));
sCircle.color = Color.cyan;
sCircle.x = Futile.screen.halfWidth + 100;
sCircle.y = Futile.screen.halfHeight;
Futile.stage.AddChild(sCircle);
}
}
using UnityEngine;
using System;
using System.Collections.Generic;
public class WTPolygonData
{
public bool shouldUseSmoothSphereCollisions = false; //set to true manually if needed
private Vector2[] polygonVertices_;
public int[] polygonTriangles {get; private set;} //a list of triangle polygons (each one is an array of int triangle indices)
public WTPolygonData (Vector2[] vertices)
{
this.polygonVertices = vertices;
}
public Vector2[] polygonVertices { //VERTICES MUST BE PROVIDED IN CLOCKWISE ORDER!
get {return polygonVertices_;}
set {
polygonVertices_ = value;
polygonTriangles = FPUtils.Triangulate(polygonVertices_);
}
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class WTPolygonSprite : FSprite
{
public WTPolygonData polygonData {get; private set;}
private int _triangleCount;
private Vector2 _uvTopLeft;
private Vector2 _uvBottomLeft;
private Vector2 _uvBottomRight;
public WTPolygonSprite (WTPolygonData polygonData) {
_element = Futile.atlasManager.GetElementWithName("Futile_White");
_isAlphaDirty = true;
UpdateWithData(polygonData);
}
// Call this method if you want to change the points of the polygon after it already exists.
public void UpdateWithData(WTPolygonData newData) {
this.polygonData = newData;
if (polygonData != null) {
RefreshVertices();
_triangleCount = this.polygonData.polygonTriangles.Length / 3;
Init(FFacetType.Triangle, _element, _triangleCount);
_isMatrixDirty = true;
_isAlphaDirty = true;
_areLocalVerticesDirty = true;
Redraw(true, false);
}
}
override public void Redraw(bool shouldForceDirty, bool shouldUpdateDepth)
{
bool wasMatrixDirty = _isMatrixDirty;
bool wasAlphaDirty = _isAlphaDirty;
UpdateDepthMatrixAlpha(shouldForceDirty, shouldUpdateDepth);
if(shouldUpdateDepth)
{
UpdateFacets();
}
if(wasMatrixDirty || shouldForceDirty || shouldUpdateDepth)
{
_isMeshDirty = true;
}
if(wasAlphaDirty || shouldForceDirty)
{
_isMeshDirty = true;
_color.ApplyMultipliedAlpha(ref _alphaColor, _concatenatedAlpha);
}
if(_areLocalVerticesDirty)
{
UpdateLocalVertices();
}
if(_isMeshDirty)
{
PopulateRenderLayer();
}
}
override public void HandleElementChanged()
{
_areLocalVerticesDirty = true;
}
// This is being overridden because if the vertice data gets changed for
// some reason, it needs to update some things.
override public void UpdateLocalVertices()
{
_areLocalVerticesDirty = false;
_uvTopLeft = _element.uvTopLeft;
_uvBottomLeft = _element.uvBottomLeft;
_uvBottomRight = _element.uvBottomRight;
int[] trianglePolygons = polygonData.polygonTriangles;
_triangleCount = trianglePolygons.Length / 3;
if(_numberOfFacetsNeeded != _triangleCount)
{
_numberOfFacetsNeeded = _triangleCount;
if(_isOnStage) _stage.HandleFacetsChanged();
}
_isMeshDirty = true;
}
// This is where the polygon is actually "drawn"
override public void PopulateRenderLayer()
{
if(_isOnStage && _firstFacetIndex != -1)
{
_isMeshDirty = false;
Vector3[] vertices = _renderLayer.vertices;
Vector2[] uvs = _renderLayer.uvs;
Color[] colors = _renderLayer.colors;
Vector2[] polygonVertices = polygonData.polygonVertices;
int[] trianglePolygons = polygonData.polygonTriangles;
int nextTriangleIndex = _firstFacetIndex;
int polyTriangleCount = trianglePolygons.Length / 3;
for(int t = 0; t < polyTriangleCount; t++)
{
int vertexIndex0 = nextTriangleIndex*3;
int vertexIndex1 = vertexIndex0 + 1;
int vertexIndex2 = vertexIndex0 + 2;
int threeT = t*3;
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref vertices[vertexIndex0], polygonVertices[trianglePolygons[threeT]],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref vertices[vertexIndex1], polygonVertices[trianglePolygons[threeT+1]],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref vertices[vertexIndex2], polygonVertices[trianglePolygons[threeT+2]],0);
uvs[vertexIndex0] = _uvBottomLeft;
uvs[vertexIndex1] = _uvTopLeft;
uvs[vertexIndex2] = _uvBottomRight;
colors[vertexIndex0] = _alphaColor;
colors[vertexIndex1] = _alphaColor;
colors[vertexIndex2] = _alphaColor;
nextTriangleIndex++;
}
_renderLayer.HandleVertsChange();
}
}
// If for some reason you change the WTPolygonData for this polygon sprite, make sure to call this so
// it refreshes them and knows what to draw. There may be ways to make this more optimized for performance
// that I need to look into.
public void RefreshVertices() {
if(_isOnStage && _firstFacetIndex != -1) {
_renderLayer.Open();
// Adding more facets than necessary because for some reason sometimes there aren't enough.
// I need to look into this to figure out a better way to handle this.
_renderLayer.GetNextFacetIndex(_triangleCount * 2);
Vector3[] vertices = _renderLayer.vertices;
for (int i = 0; i < vertices.Length; i++) {
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref vertices[i], Vector2.zero, 0);
}
}
}
// This method returns the polygon data for the sprite after taking into account the sprite's
// rotation, scale, and position. This is useful to get the actual vertice points that are
// being shown on screen rather than the unaltered ones. CAUTION: this does not take any
// parent container rotation, scale, or position into account. It just gets the data based
// on the sprite's own properties. This method has NOT been optimized for performance yet.
// There are ways to make it faster that I will update it with in the future.
public WTPolygonData GetAdjustedPolygonData() {
Vector2[] newPolygonVertices = new Vector2[polygonData.polygonVertices.Length];
for (int i = 0; i < newPolygonVertices.Length; i++) {
newPolygonVertices[i] = new Vector2(polygonData.polygonVertices[i].x, polygonData.polygonVertices[i].y);
}
for (int i = 0; i < newPolygonVertices.Length; i++) {
Vector2 v = newPolygonVertices[i];
v.x *= _scaleX;
v.y *= _scaleY;
Vector2 origV = v;
// rotation needs to be negative because of the way rotation works in futile (clockwise rather than counter clockwise)
float cosAngle = Mathf.Cos(-_rotation);
float sinAngle = Mathf.Sin(-_rotation);
v.x = origV.x * cosAngle - origV.y * sinAngle;
v.y = origV.x * sinAngle + origV.y * cosAngle;
v.x += this.x;
v.y += this.y;
newPolygonVertices[i] = v;
}
return new WTPolygonData(newPolygonVertices);
}
}
using UnityEngine;
using System.Collections;
public static class WTUtils {
// Line segment intersection based on this: http://www.pdas.com/lineint.html
public static bool IntersectLineSegments(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) {
Vector2 s1 = p1 - p0;
Vector2 s2 = p3 - p2;
float s, t, denom;
denom = -s2.x * s1.y + s1.x * s2.y;
if (denom == 0) {
// divide by zero
return false;
}
s = (-s1.y * (p0.x - p2.x) + s1.x * (p0.y - p2.y)) / denom;
t = ( s2.x * (p0.y - p2.y) - s2.y * (p0.x - p2.x)) / denom;
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) return true;
return false;
}
// Intersect circle/polygon algorithm based on this: http://gamedev.stackexchange.com/questions/7735/how-to-find-if-circle-and-concave-polygon-intersect
public static bool IntersectCirclePolygon(Vector2 circlePos, float circleRadius, Vector2[] polygonVertices) {
float xPolygonMin = float.MaxValue;
float xPolygonMax = float.MinValue;
float yPolygonMin = float.MaxValue;
float yPolygonMax = float.MinValue;
for (int i = 0; i < polygonVertices.Length; i++) {
if (IntersectCircleLineSegment(circlePos, circleRadius, polygonVertices[i], polygonVertices[(i+1)%polygonVertices.Length])) return true;
Vector2 v = polygonVertices[i];
xPolygonMin = Mathf.Min(xPolygonMin, v.x);
xPolygonMax = Mathf.Max(xPolygonMax, v.x);
yPolygonMin = Mathf.Min(yPolygonMin, v.y);
yPolygonMax = Mathf.Max(yPolygonMax, v.y);
}
// Expand the polygon just a bit to make sure everything is encompassed.
float extra = 5;
xPolygonMin += extra;
xPolygonMax += extra;
yPolygonMin += extra;
yPolygonMax += extra;
Vector2 circRayRight_p0 = circlePos;
Vector2 circRayRight_p1 = new Vector2(10000, circlePos.y); // Extend the ray super far to the right
float numIntersections = 0;
for (int i = 0; i < polygonVertices.Length; i++) {
Vector2 poly_p0 = polygonVertices[i];
Vector2 poly_p1 = polygonVertices[(i+1)%polygonVertices.Length];
if (poly_p0.y == poly_p1.y) continue;
if (IntersectLineSegments(circRayRight_p0, circRayRight_p1, polygonVertices[i], polygonVertices[(i+1)%polygonVertices.Length])) numIntersections++;
}
if (numIntersections % 2 == 1) return true;
else return false;
}
public static bool IntersectCircleLineSegment(Vector2 circlePos, float circleRadius, Vector2 segPointA, Vector2 segPointB) {
Vector2 upperPoint;
if (segPointA.y > segPointB.y) upperPoint = segPointA;
else upperPoint = segPointB;
Vector2 segVector = segPointB - segPointA;
Vector2 aToCircVector = circlePos - segPointA;
float closestSegPointMagnitude = Vector2.Dot(aToCircVector, segVector.normalized);
Vector2 closestSegPoint;
if (closestSegPointMagnitude < 0) closestSegPoint = segPointA;
else if (closestSegPointMagnitude > segVector.magnitude) closestSegPoint = segPointB;
else closestSegPoint = segPointA + closestSegPointMagnitude * segVector.normalized;
Vector2 closestToCircVector = circlePos - closestSegPoint;
// Don't intersect with top point of line segment (keep it "open")
if (upperPoint.Equals(closestSegPoint)) {
if (closestToCircVector.magnitude < circleRadius) return true;
}
else {
if (closestToCircVector.magnitude <= circleRadius) return true;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment