textured_line.dart 8.03 KB
Newer Older
1
part of flutter_sprites;
2 3 4 5 6 7 8 9

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;

10
  @override
Adam Barth's avatar
Adam Barth committed
11
  void paint(Canvas canvas) {
12 13 14 15 16 17 18 19 20 21 22 23 24
    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;

25
  void set points(List<Point> points) {
26 27 28 29 30 31 32 33 34 35
    _points = points;
    _calculatedTextureStops = null;
  }

  List<Color> colors;
  List<double> widths;
  Texture _texture;

  Texture get texture => _texture;

36
  void set texture(Texture texture) {
37 38 39 40 41
    _texture = texture;
    if (texture == null) {
      _cachedPaint = new Paint();
    } else {
      Matrix4 matrix = new Matrix4.identity();
42 43
      ImageShader shader = new ImageShader(texture.image,
        TileMode.repeated, TileMode.repeated, matrix.storage);
44

45 46
      _cachedPaint = new Paint()
        ..shader = shader;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
    }
  }

  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;

72
  double get textureLoopLength => textureLoopLength;
73

74
  void set textureLoopLength(double textureLoopLength) {
75 76 77 78
    _textureLoopLength = textureLoopLength;
    _calculatedTextureStops = null;
  }

79 80
  bool removeArtifacts = true;

81
  TransferMode transferMode = TransferMode.srcOver;
82

83 84
  Paint _cachedPaint = new Paint();

Adam Barth's avatar
Adam Barth committed
85
  void paint(Canvas canvas) {
86 87 88 89 90 91 92
    // Check input values
    assert(_points != null);
    if (_points.length < 2) return;

    assert(_points.length == colors.length);
    assert(_points.length == widths.length);

93 94
    _cachedPaint.transferMode = transferMode;

95
    // Calculate normals
Hixie's avatar
Hixie committed
96
    List<Vector2> vectors = <Vector2>[];
97 98 99 100 101
    for (Point pt in _points) {
      vectors.add(new Vector2(pt.x, pt.y));
    }
    List<Vector2> miters = _computeMiterList(vectors, false);

Hixie's avatar
Hixie committed
102
    List<Point> vertices = <Point>[];
103
    List<int> indices = <int>[];
Hixie's avatar
Hixie committed
104
    List<Color> verticeColors = <Color>[];
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    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;
Hixie's avatar
Hixie committed
125
      textureCoordinates = <Point>[];
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

      // 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;
154 155
      indices.addAll(<int>[lastIndex0, lastIndex1, currentIndex0]);
      indices.addAll(<int>[lastIndex1, currentIndex1, currentIndex0]);
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

      // 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;
    }

173
    canvas.drawVertices(VertexMode.triangles, vertices, textureCoordinates, verticeColors, TransferMode.modulate, indices, _cachedPaint);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  }

  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);

190 191 192 193 194 195 196 197 198 199
    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) {
200
        if (GameMath.distanceBetweenPoints(vertex0, intersection) < GameMath.distanceBetweenPoints(vertex1, intersection)) {
201 202 203 204 205 206 207 208 209
          vertex0 = oldVertex0;
        } else {
          vertex1 = oldVertex1;
        }
      }
    }

    vertices.add(vertex0);
    vertices.add(vertex1);
210 211 212
  }

  void _calculateTextureStops() {
Hixie's avatar
Hixie committed
213
    List<double> stops = <double>[];
214 215 216 217 218 219 220 221 222 223
    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];

224
      double dist = GameMath.distanceBetweenPoints(lastPoint, currentPoint);
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
      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();

244 245 246 247 248 249 250
  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;
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
  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) {
Hixie's avatar
Hixie committed
266
  List<Vector2> out = <Vector2>[];
Ian Hickson's avatar
Ian Hickson committed
267
  Vector2 curNormal;
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

  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;
}