textured_line.dart 8.05 KB
Newer Older
1
part of flutter_sprites;
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

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(PaintingCanvas 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;

  set points(List<Point> points) {
    _points = points;
    _calculatedTextureStops = null;
  }

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

  Texture get texture => _texture;

  set texture(Texture texture) {
    _texture = texture;
    if (texture == null) {
      _cachedPaint = new Paint();
    } else {
      Matrix4 matrix = new Matrix4.identity();
41 42
      ui.ImageShader shader = new ui.ImageShader(texture.image,
        ui.TileMode.repeated, ui.TileMode.repeated, matrix.storage);
43

44 45
      _cachedPaint = new Paint()
        ..shader = shader;
46 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 72 73 74 75 76 77
    }
  }

  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;

  get textureLoopLength => textureLoopLength;

  set textureLoopLength(double textureLoopLength) {
    _textureLoopLength = textureLoopLength;
    _calculatedTextureStops = null;
  }

78 79
  bool removeArtifacts = true;

80
  ui.TransferMode transferMode = ui.TransferMode.srcOver;
81

82 83 84 85 86 87 88 89 90 91
  Paint _cachedPaint = new Paint();

  void paint(PaintingCanvas canvas) {
    // Check input values
    assert(_points != null);
    if (_points.length < 2) return;

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

92 93
    _cachedPaint.transferMode = transferMode;

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

Hixie's avatar
Hixie committed
101 102 103
    List<Point> vertices = <Point>[];
    List<int> indicies = <int>[];
    List<Color> verticeColors = <Color>[];
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    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
124
      textureCoordinates = <Point>[];
125 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

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

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

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

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

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

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

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

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

243 244 245 246 247 248 249
  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;
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
  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
265
  List<Vector2> out = <Vector2>[];
266 267 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
  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;
}