blob: 11c70f08f5ee353bf34c875dcbb1d5fe5a9b90e2 [file] [log] [blame]
part of flutter_sprites;
class TexturedLine extends Node {
TexturedLine(List<Point> points, List<Color> colors, List<double> widths, [Texture texture, List<double> textureStops]) {
painter = new TexturedLinePainter(points, colors, widths, texture, textureStops);
}
TexturedLinePainter painter;
void paint(Canvas canvas) {
painter.paint(canvas);
}
}
class TexturedLinePainter {
TexturedLinePainter(this._points, this.colors, this.widths, [Texture texture, this.textureStops]) {
this.texture = texture;
}
List<Point> _points;
List<Point> get points => _points;
void set points(List<Point> points) {
_points = points;
_calculatedTextureStops = null;
}
List<Color> colors;
List<double> widths;
Texture _texture;
Texture get texture => _texture;
void set texture(Texture texture) {
_texture = texture;
if (texture == null) {
_cachedPaint = new Paint();
} else {
Matrix4 matrix = new Matrix4.identity();
ImageShader shader = new ImageShader(texture.image,
TileMode.repeated, TileMode.repeated, matrix.storage);
_cachedPaint = new Paint()
..shader = shader;
}
}
List<double> textureStops;
List<double> _calculatedTextureStops;
List<double> get calculatedTextureStops {
if (_calculatedTextureStops == null)
_calculateTextureStops();
return _calculatedTextureStops;
}
double _length;
double get length {
if (_calculatedTextureStops == null)
_calculateTextureStops();
return _length;
}
double textureStopOffset = 0.0;
double _textureLoopLength;
double get textureLoopLength => textureLoopLength;
void set textureLoopLength(double textureLoopLength) {
_textureLoopLength = textureLoopLength;
_calculatedTextureStops = null;
}
bool removeArtifacts = true;
TransferMode transferMode = TransferMode.srcOver;
Paint _cachedPaint = new Paint();
void paint(Canvas canvas) {
// Check input values
assert(_points != null);
if (_points.length < 2) return;
assert(_points.length == colors.length);
assert(_points.length == widths.length);
_cachedPaint.transferMode = transferMode;
// Calculate normals
List<Vector2> vectors = <Vector2>[];
for (Point pt in _points) {
vectors.add(new Vector2(pt.x, pt.y));
}
List<Vector2> miters = _computeMiterList(vectors, false);
List<Point> vertices = <Point>[];
List<int> indicies = <int>[];
List<Color> verticeColors = <Color>[];
List<Point> textureCoordinates;
double textureTop;
double textureBottom;
List<double> stops;
// Add first point
Point lastPoint = _points[0];
Vector2 lastMiter = miters[0];
// Add vertices and colors
_addVerticesForPoint(vertices, lastPoint, lastMiter, widths[0]);
verticeColors.add(colors[0]);
verticeColors.add(colors[0]);
if (texture != null) {
assert(texture.rotated == false);
// Setup for calculating texture coordinates
textureTop = texture.frame.top;
textureBottom = texture.frame.bottom;
textureCoordinates = <Point>[];
// Use correct stops
if (textureStops != null) {
assert(_points.length == textureStops.length);
stops = textureStops;
} else {
if (_calculatedTextureStops == null) _calculateTextureStops();
stops = _calculatedTextureStops;
}
// Texture coordinate points
double xPos = _xPosForStop(stops[0]);
textureCoordinates.add(new Point(xPos, textureTop));
textureCoordinates.add(new Point(xPos, textureBottom));
}
// Add the rest of the points
for (int i = 1; i < _points.length; i++) {
// Add vertices
Point currentPoint = _points[i];
Vector2 currentMiter = miters[i];
_addVerticesForPoint(vertices, currentPoint, currentMiter, widths[i]);
// Add references to the triangles
int lastIndex0 = (i - 1) * 2;
int lastIndex1 = (i - 1) * 2 + 1;
int currentIndex0 = i * 2;
int currentIndex1 = i * 2 + 1;
indicies.addAll(<int>[lastIndex0, lastIndex1, currentIndex0]);
indicies.addAll(<int>[lastIndex1, currentIndex1, currentIndex0]);
// Add colors
verticeColors.add(colors[i]);
verticeColors.add(colors[i]);
if (texture != null) {
// Texture coordinate points
double xPos = _xPosForStop(stops[i]);
textureCoordinates.add(new Point(xPos, textureTop));
textureCoordinates.add(new Point(xPos, textureBottom));
}
// Update last values
lastPoint = currentPoint;
lastMiter = currentMiter;
}
canvas.drawVertices(VertexMode.triangles, vertices, textureCoordinates, verticeColors, TransferMode.modulate, indicies, _cachedPaint);
}
double _xPosForStop(double stop) {
if (_textureLoopLength == null) {
return texture.frame.left + texture.frame.width * (stop - textureStopOffset);
} else {
return texture.frame.left + texture.frame.width * (stop - textureStopOffset * (_textureLoopLength / length)) * (length / _textureLoopLength);
}
}
void _addVerticesForPoint(List<Point> vertices, Point point, Vector2 miter, double width) {
double halfWidth = width / 2.0;
Offset offset0 = new Offset(miter[0] * halfWidth, miter[1] * halfWidth);
Offset offset1 = new Offset(-miter[0] * halfWidth, -miter[1] * halfWidth);
Point vertex0 = point + offset0;
Point vertex1 = point + offset1;
int vertexCount = vertices.length;
if (removeArtifacts && vertexCount >= 2) {
Point oldVertex0 = vertices[vertexCount - 2];
Point oldVertex1 = vertices[vertexCount - 1];
Point intersection = GameMath.lineIntersection(oldVertex0, oldVertex1, vertex0, vertex1);
if (intersection != null) {
if (GameMath.distanceBetweenPoints(vertex0, intersection) < GameMath.distanceBetweenPoints(vertex1, intersection)) {
vertex0 = oldVertex0;
} else {
vertex1 = oldVertex1;
}
}
}
vertices.add(vertex0);
vertices.add(vertex1);
}
void _calculateTextureStops() {
List<double> stops = <double>[];
double length = 0.0;
// Add first stop
stops.add(0.0);
// Calculate distance to each point from the first point along the line
for (int i = 1; i < _points.length; i++) {
Point lastPoint = _points[i - 1];
Point currentPoint = _points[i];
double dist = GameMath.distanceBetweenPoints(lastPoint, currentPoint);
length += dist;
stops.add(length);
}
// Normalize the values in the range [0.0, 1.0]
for (int i = 1; i < points.length; i++) {
stops[i] = stops[i] / length;
new Point(512.0, 512.0);
}
_calculatedTextureStops = stops;
_length = length;
}
}
Vector2 _computeMiter(Vector2 lineA, Vector2 lineB) {
Vector2 miter = new Vector2(- (lineA[1] + lineB[1]), lineA[0] + lineB[0]);
miter.normalize();
double dot = dot2(miter, new Vector2(-lineA[1], lineA[0]));
if (dot.abs() < 0.1) {
miter = _vectorNormal(lineA).normalize();
return miter;
}
double miterLength = 1.0 / dot;
miter = miter.scale(miterLength);
return miter;
}
Vector2 _vectorNormal(Vector2 v) {
return new Vector2(-v[1], v[0]);
}
Vector2 _vectorDirection(Vector2 a, Vector2 b) {
Vector2 result = a - b;
return result.normalize();
}
List<Vector2> _computeMiterList(List<Vector2> points, bool closed) {
List<Vector2> out = <Vector2>[];
Vector2 curNormal = null;
if (closed) {
points = new List<Vector2>.from(points);
points.add(points[0]);
}
int total = points.length;
for (int i = 1; i < total; i++) {
Vector2 last = points[i - 1];
Vector2 cur = points[i];
Vector2 next = (i < total - 1) ? points[i + 1] : null;
Vector2 lineA = _vectorDirection(cur, last);
if (curNormal == null) {
curNormal = _vectorNormal(lineA);
}
if (i == 1) {
out.add(curNormal);
}
if (next == null) {
curNormal = _vectorNormal(lineA);
out.add(curNormal);
} else {
Vector2 lineB = _vectorDirection(next, cur);
Vector2 miter = _computeMiter(lineA, lineB);
out.add(miter);
}
}
return out;
}