// 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. part of flutter_sprites; /// Used by [EffectLine] to determine how the width of the line is calculated. enum EffectLineWidthMode { /// Linear interpolation between minWidth at the start and maxWidth at the /// end of the line. linear, /// Creates a barrel shaped line, with minWidth at the end points of the line /// and maxWidth at the middle. barrel, } /// Used by [EffectLine] to determine how the texture of the line is animated. enum EffectLineAnimationMode { /// The texture of the line isn't animated. none, /// The texture of the line is scrolling. scroll, /// 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. random, } /// 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. class EffectLine extends Node { /// Creates a new EffectLine with the specified parameters. Only the /// [texture] parameter is required, all other parameters are optional. EffectLine({ this.texture: null, this.transferMode: TransferMode.dstOver, List<Point> points, this.widthMode : EffectLineWidthMode.linear, this.minWidth: 10.0, this.maxWidth: 10.0, this.widthGrowthSpeed: 0.0, this.animationMode: EffectLineAnimationMode.none, this.scrollSpeed: 0.1, double scrollStart: 0.0, this.fadeDuration: null, this.fadeAfterDelay: null, this.textureLoopLength: null, this.simplify: true, ColorSequence colorSequence }) { if (points == null) this.points = <Point>[]; else this.points = points; _colorSequence = colorSequence; if (_colorSequence == null) { _colorSequence = new ColorSequence.fromStartAndEndColor( const Color(0xffffffff), const Color(0xffffffff) ); } _offset = scrollStart; _painter = new TexturedLinePainter(points, _colors, _widths, texture); _painter.textureLoopLength = textureLoopLength; } /// The texture used to draw the line. final Texture texture; /// The transfer mode used to draw the line, default is /// [TransferMode.dstOver]. final TransferMode transferMode; /// Mode used to calculate the width of the line. final EffectLineWidthMode widthMode; /// The width of the line at its thinnest point. final double minWidth; /// The width of the line at its thickest point. final double maxWidth; /// The speed at which the line is growing, defined in points per second. final double widthGrowthSpeed; /// The mode used to animate the texture of the line. final EffectLineAnimationMode animationMode; /// The speed of which the texture of the line is scrolling. This property /// is only used if the [animationMode] is set to /// [EffectLineAnimationMode.scroll]. final double scrollSpeed; /// Color gradient used to draw the line, from start to finish. ColorSequence get colorSequence => _colorSequence; ColorSequence _colorSequence; /// 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. List<Point> get points => _points; set points(List<Point> points) { _points = points; _pointAges = <double>[]; for (int i = 0; i < _points.length; i++) { _pointAges.add(0.0); } } List<Point> _points; List<double> _pointAges; List<Color> _colors; List<double> _widths; /// The time it takes for an added point to fade out. It's total life time is /// [fadeDuration] + [fadeAfterDelay]. final double fadeDuration; /// The time it takes until an added point starts to fade out. final double fadeAfterDelay; /// The length, in points, that the texture is stretched to. If the /// textureLoopLength is shorter than the line, the texture will be looped. final double textureLoopLength; /// 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. final bool simplify; TexturedLinePainter _painter; double _offset = 0.0; @override void update(double dt) { // Update scrolling position if (animationMode == EffectLineAnimationMode.scroll) { _offset += dt * scrollSpeed; _offset %= 1.0; } else if (animationMode == EffectLineAnimationMode.random) { _offset = randomDouble(); } // Update age of line points and remove if neccesasry if (fadeDuration != null && fadeAfterDelay != null) { // Increase age of points for (int i = _points.length - 1; i >= 0; i--) { _pointAges[i] += dt; } // 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) { double dist = GameMath.distanceBetweenPoints(_points[0], _points[1]); _offset = (_offset - (dist / textureLoopLength)) % 1.0; if (_offset < 0.0) _offset += 1; } // Remove the point _pointAges.removeAt(0); _points.removeAt(0); } } } @override void paint(Canvas canvas) { if (points.length < 2) return; _painter.points = points; // Calculate colors List<double> stops = _painter.calculatedTextureStops; List<Color> colors = <Color>[]; 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 List<double> widths = <double>[]; for (int i = 0; i < stops.length; i++) { double stop = stops[i]; double growth = math.max(widthGrowthSpeed * _pointAges[i], 0.0); if (widthMode == EffectLineWidthMode.linear) { double width = minWidth + (maxWidth - minWidth) * stop + growth; widths.add(width); } else if (widthMode == EffectLineWidthMode.barrel) { double width = minWidth + math.sin(stop * math.PI) * (maxWidth - minWidth) + growth; widths.add(width); } } _painter.widths = widths; _painter.textureStopOffset = _offset; _painter.paint(canvas); } /// Adds a new point to the end of the line. 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; if (simplify && points.length >= 2 && GameMath.distanceBetweenPoints(point, points[points.length - 2]) < 10.0) { // 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] ); // If the point is on the line, remove it if (dist2 < 1.0) { _points.removeAt(_points.length - 1); } } // Add point and point's age _points.add(point); _pointAges.add(0.0); } 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))); } }