effect_line.dart 8.23 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
part of flutter_sprites;
6

7
/// Used by [EffectLine] to determine how the width of the line is calculated.
8
enum EffectLineWidthMode {
9 10 11

  /// Linear interpolation between minWidth at the start and maxWidth at the
  /// end of the line.
12
  linear,
13 14 15

  /// Creates a barrel shaped line, with minWidth at the end points of the line
  /// and maxWidth at the middle.
16 17 18
  barrel,
}

19
/// Used by [EffectLine] to determine how the texture of the line is animated.
20
enum EffectLineAnimationMode {
21 22

  /// The texture of the line isn't animated.
23
  none,
24 25

  /// The texture of the line is scrolling.
26
  scroll,
27 28 29

  /// The texture of the line is set to a random position at every frame. This
  /// mode is useful for creating flashing or electricity styled effects.
30 31 32
  random,
}

33 34 35
/// The EffectLine class is using the [TexturedLine] class to draw animated
/// lines. These can be used to draw things such as smoke trails, electricity
/// effects, or other animated types of lines.
36 37
class EffectLine extends Node {

38 39
  /// Creates a new EffectLine with the specified parameters. Only the
  /// [texture] parameter is required, all other parameters are optional.
40 41
  EffectLine({
    this.texture: null,
42
    this.transferMode: TransferMode.dstOver,
43 44 45 46
    List<Point> points,
    this.widthMode : EffectLineWidthMode.linear,
    this.minWidth: 10.0,
    this.maxWidth: 10.0,
47
    this.widthGrowthSpeed: 0.0,
48 49
    this.animationMode: EffectLineAnimationMode.none,
    this.scrollSpeed: 0.1,
50
    double scrollStart: 0.0,
51 52 53 54 55 56
    this.fadeDuration: null,
    this.fadeAfterDelay: null,
    this.textureLoopLength: null,
    this.simplify: true,
    ColorSequence colorSequence
  }) {
Hixie's avatar
Hixie committed
57 58 59 60
    if (points == null)
      this.points = <Point>[];
    else
      this.points = points;
61 62

    _colorSequence = colorSequence;
Hixie's avatar
Hixie committed
63
    if (_colorSequence == null) {
64
      _colorSequence = new ColorSequence.fromStartAndEndColor(
65 66
        const Color(0xffffffff),
        const Color(0xffffffff)
Hixie's avatar
Hixie committed
67 68
      );
    }
69

70 71
    _offset = scrollStart;

72 73 74 75
    _painter = new TexturedLinePainter(points, _colors, _widths, texture);
    _painter.textureLoopLength = textureLoopLength;
  }

76
  /// The texture used to draw the line.
77 78
  final Texture texture;

79 80
  /// The transfer mode used to draw the line, default is
  /// [TransferMode.dstOver].
81
  final TransferMode transferMode;
82

83
  /// Mode used to calculate the width of the line.
84
  final EffectLineWidthMode widthMode;
85 86

  /// The width of the line at its thinnest point.
87
  final double minWidth;
88 89

  /// The width of the line at its thickest point.
90
  final double maxWidth;
91 92

  /// The speed at which the line is growing, defined in points per second.
93
  final double widthGrowthSpeed;
94

95
  /// The mode used to animate the texture of the line.
96
  final EffectLineAnimationMode animationMode;
97 98 99 100

  /// The speed of which the texture of the line is scrolling. This property
  /// is only used if the [animationMode] is set to
  /// [EffectLineAnimationMode.scroll].
101
  final double scrollSpeed;
102 103

  /// Color gradient used to draw the line, from start to finish.
104 105
  ColorSequence get colorSequence => _colorSequence;

106
  ColorSequence _colorSequence;
107

108 109 110
  /// List of points that make up the line. Typically, you will only want to
  /// set this at the beginning. Then use [addPoint] to add additional points
  /// to the line.
111 112
  List<Point> get points => _points;

113
  set points(List<Point> points) {
114
    _points = points;
Hixie's avatar
Hixie committed
115
    _pointAges = <double>[];
116 117 118 119 120
    for (int i = 0; i < _points.length; i++) {
      _pointAges.add(0.0);
    }
  }

121 122
  List<Point> _points;

123 124 125 126
  List<double> _pointAges;
  List<Color> _colors;
  List<double> _widths;

127 128
  /// The time it takes for an added point to fade out. It's total life time is
  /// [fadeDuration] + [fadeAfterDelay].
129
  final double fadeDuration;
130 131

  /// The time it takes until an added point starts to fade out.
132 133
  final double fadeAfterDelay;

134 135
  /// The length, in points, that the texture is stretched to. If the
  /// textureLoopLength is shorter than the line, the texture will be looped.
136 137
  final double textureLoopLength;

138 139 140
  /// True if the line should be simplified by removing points that are close
  /// to other points. This makes drawing faster, but can result in a slight
  /// jittering effect when points are added.
141 142 143 144 145
  final bool simplify;

  TexturedLinePainter _painter;
  double _offset = 0.0;

146
  @override
147 148 149 150 151 152 153 154 155
  void update(double dt) {
    // Update scrolling position
    if (animationMode == EffectLineAnimationMode.scroll) {
      _offset += dt * scrollSpeed;
      _offset %= 1.0;
    } else if (animationMode == EffectLineAnimationMode.random) {
      _offset = randomDouble();
    }

156
    // Update age of line points and remove if neccesasry
157
    if (fadeDuration != null && fadeAfterDelay != null) {
158
      // Increase age of points
159 160
      for (int i = _points.length - 1; i >= 0; i--) {
        _pointAges[i] += dt;
161 162 163 164 165 166
      }

      // Check if the first/oldest point should be removed
      while(_points.length > 0 && _pointAges[0] > (fadeDuration + fadeAfterDelay)) {
        // Update scroll if it isn't the last and only point that is about to removed
        if (_points.length > 1 && textureLoopLength != null) {
167
          double dist = GameMath.distanceBetweenPoints(_points[0], _points[1]);
168 169
          _offset = (_offset - (dist / textureLoopLength)) % 1.0;
          if (_offset < 0.0) _offset += 1;
170
        }
171 172 173 174

        // Remove the point
        _pointAges.removeAt(0);
        _points.removeAt(0);
175 176 177 178
      }
    }
  }

179
  @override
Adam Barth's avatar
Adam Barth committed
180
  void paint(Canvas canvas) {
181 182 183 184 185 186 187
    if (points.length < 2) return;

    _painter.points = points;

    // Calculate colors
    List<double> stops = _painter.calculatedTextureStops;

Hixie's avatar
Hixie committed
188
    List<Color> colors = <Color>[];
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    for (int i = 0; i < stops.length; i++) {
      double stop = stops[i];
      Color color = _colorSequence.colorAtPosition(stop);

      if (fadeDuration != null && fadeAfterDelay != null) {
        double age = _pointAges[i];
        if (age > fadeAfterDelay) {
          double fade = 1.0 - (age - fadeAfterDelay) / fadeDuration;
          int alpha = (color.alpha * fade).toInt().clamp(0, 255);
          color = new Color.fromARGB(alpha, color.red, color.green, color.blue);
        }
      }
      colors.add(color);
    }
    _painter.colors = colors;

    // Calculate widths
Hixie's avatar
Hixie committed
206
    List<double> widths = <double>[];
207 208 209
    for (int i = 0; i < stops.length; i++) {
      double stop = stops[i];
      double growth = math.max(widthGrowthSpeed * _pointAges[i], 0.0);
210
      if (widthMode == EffectLineWidthMode.linear) {
211
        double width = minWidth + (maxWidth - minWidth) * stop + growth;
212 213
        widths.add(width);
      } else if (widthMode == EffectLineWidthMode.barrel) {
214
        double width = minWidth + math.sin(stop * math.PI) * (maxWidth - minWidth) + growth;
215 216 217 218 219 220 221 222 223 224
        widths.add(width);
      }
    }
    _painter.widths = widths;

    _painter.textureStopOffset = _offset;

    _painter.paint(canvas);
  }

225
  /// Adds a new point to the end of the line.
226 227 228 229 230
  void addPoint(Point point) {
    // Skip duplicate points
    if (points.length > 0 && point.x == points[points.length - 1].x && point.y == points[points.length - 1].y)
      return;

231
    if (simplify && points.length >= 2 && GameMath.distanceBetweenPoints(point, points[points.length - 2]) < 10.0) {
232 233 234 235 236 237 238 239 240
      // Check if we should remove last point before adding the new one

      // Calculate the square distance from the middle point to the line of the
      // new point and the second to last point
      double dist2 = _distToSeqment2(
        points[points.length - 1],
        point,
        points[points.length - 2]
      );
241

242 243 244 245
      // If the point is on the line, remove it
      if (dist2 < 1.0) {
        _points.removeAt(_points.length - 1);
      }
246 247 248 249 250 251
    }

    // Add point and point's age
    _points.add(point);
    _pointAges.add(0.0);
  }
252 253 254 255 256 257 258 259 260 261 262 263 264

  double _sqr(double x) => x * x;

  double _dist2(Point v, Point w) => _sqr(v.x - w.x) + _sqr(v.y - w.y);

  double _distToSeqment2(Point p, Point v, Point w) {
    double l2 = _dist2(v, w);
    if (l2 == 0.0) return _dist2(p, v);
    double t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
    if (t < 0) return _dist2(p, v);
    if (t > 1) return _dist2(p, w);
    return _dist2(p, new Point(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)));
  }
265
}