Code covered by the BSD License  

Highlights from
Fireworks GUI

image thumbnail

Fireworks GUI

by

 

30 Dec 2010 (Updated )

Enjoy some fireworks (with sound) with this interactive GUI

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

helper.Fireworks
classdef Fireworks < handle
    % Fireworks     Class for creating fireworks objects
    %
    % This class creates Fireworks objects that can be launched (and drawn) on
    % an axes. It is used by the FireworksAxes class, and should not be called
    % by itself. The FireworksAxes class has a "newFireworks" method that calls
    % this class to construct fireworks. That method can be called with
    % additional Property-Value pairs to specify the shape, size, speed, and
    % position of the fireworks.
    %
    % Fireworks Properties:
    %   (Public)
    %   HaveSound       - Whether there is sound
    %
    %   (Protected)
    %   FWAxes          - FireworksAxes object that this object is tied to
    %   FinishedFcn     - Callback that gets called after fireworks completion
    %   SpeedFactor     - Speed of the fireworks
    %
    %   (Constant)
    %   ValidFireworksType - Valid input string for FireworksType
    %
    % This class has two methods (one being the constructor), and they are
    % called internally by the FireworksAxes class.
    %
    % Fireworks Methods:
    %   helper.Fireworks - Constructor
    %   step             - Step fireworks animation
    %
    % See also FireworksAxes, FireworksAxes.newFireworks.
    
    % Author: Jiro Doke
    % Date: Jan 29, 2011
    % Copyright 2010-2012 The MathWorks, Inc.
    
    %% Properties
    properties
        % HaveSound   Whether there is sound
        %   true/false. Default is true. This indicates whether there will
        %   be sound or not.
        HaveSound = true
    end
    
    properties (SetAccess = protected)
        % FWAxes   FireworksAxes object that this object is tied to
        FWAxes
        
        % FinishedFcn   Callback that gets called after fireworks completion
        %   A function handle to a callback function that gets called after the
        %   fireworks has completed. The callback should not take any input
        %   arguments and produce no output arguments.
        FinishedFcn
                                
        % SpeedFactor   Speed of the fireworks
        %   A scalar number indicating the speed factor. The value must be
        %   between 0.5 and 1.25. A small value corresponds to faster
        %   flight. Default is a random value between 0.5 and 1.25.
        SpeedFactor
    end
    
    properties (Dependent, GetAccess = protected, Hidden)
        PetalColorIndex
    end
        
    properties (Constant, Hidden)
        Colormap = unique(getColorTable(), 'rows')
    end
    
    properties (Constant, GetAccess = protected, Hidden)
        ColorTable = getColorTable();
        Sounds = load('fireworks_sound.mat')
    end
    
    properties (Constant)
        % ValidFireworksType    Valid strings for FireworksType property
        %
        %    'symmetric', 'asymmetric', 'heart', 'matlab', 'a', 'b', 'c', 'd', 'e',
        %    'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
        %    't', 'u', 'v', 'w', 'x' 'y', 'z', '?', '!'
        %
        % See also Fireworks.FireworksType
        ValidFireworksType = [{'symmetric', 'asymmetric', 'heart', 'matlab'}, ...
            cellstr(('a':'z')')', {'?', '!'}]
    end

    properties (Access = public, Hidden)
        T_Stem
        Y_Stem
        T_Petals
        Y_Petals
        H_Stem
        H_Petals
        
        SwooshSound
        PopSound
        
        StartTime
        
        TailLength = 20
        PetalTailTime = 10
        
        Stage = 0
        
        PropChangeListener
                
    end
    
    %% Methods
    methods
        
        function obj = Fireworks(FWAxes, pr)
            
            if nargin == 0
                return;
            end
                        
            %% Create Object
            obj.FWAxes = FWAxes;
            obj.SpeedFactor = pr.SpeedFactor;
            obj.HaveSound = pr.HaveSound;
            obj.FinishedFcn = pr.FinishedFcn;
            
            [pr.ExplodeVelocity, pr.ExplodeAngle] = getCustomVelAng(pr);
            
            computeTrajectory(obj, pr);
            
            if ~ismember(pr.FireworksType, {'symmetric', 'asymmetric'})
                obj.TailLength = 5;
                obj.PetalTailTime = 5;
            end
            
        end
        
        function flyBlocking(obj)
            % flyBlocking   Animate fireworks to completion (blocking)
            %
            %   flyBlocking(FWOBJ) animates the fireworks while blocking.
            
            while(obj.step)
                drawnow;
            end
            
        end
        
        function success = step(obj)
            % step   Step fireworks animation
            %
            %   step(FWOBJ) steps the fireworks animation. Note that this
            %       is time-based animation, not frame based. For each
            %       "step" it calculates the current position based on
            %       time. For this reason, you should run this in a loop or
            %       using a timer object. OBJ can be an array of Fireworks
            %       objects, and it would step each object.
            %
            %   S = step(FWOBJ) returns true if the procedure was
            %       successful. It would return false if any of the
            %       following occurs:
            %           1. The fireworks completed.
            %           2. The object was deleted.
            %           3. The axes was deleted.
            %       S will be the same size as OBJ.
            %
            % Example:
            %   % Create 10 fireworks
            %       f = Fireworks(gca, 10);
            %
            %   % Animate at 0.1 second intervals and continue until all
            %   % have finished
            %       while any(step(f))
            %           pause(0.1);
            %       end
            %
            % See also Fireworks, Fireworks.Fireworks,
            % Fireworks.flyBlocking, Fireworks.resetFireworks,
            % Fireworks.newFireworks.
            
            % Treat method call to an array of objects
            if numel(obj) > 1
                success = false(size(obj));
                for id = 1:numel(obj)
                    success(id) = step(obj(id));
                end
                drawnow;
                return;
            end
            
            if ~isOperable(obj)
                success = false;
                return;
            end
            
            if ~ishandle(obj.FWAxes.AxH)
                delete(obj);
                success = false;
                return;
            end
                        
            success = true;
            
            switch obj.Stage
                case 3
                    checkFinishedFcn(obj);
                    success = false;
                case 0
                    if obj.HaveSound
                        %play(obj.SwooshSound);
                    end
                    obj.StartTime = clock;
                    obj.Stage = 1;
                case {1, 2}
                    et = etime(clock, obj.StartTime)*12;
                    switch obj.Stage
                        case 1
                            et = et/obj.SpeedFactor;
                            tailLength = 5;
                            if et < obj.T_Stem(end)
                                [minVal, id] = min(abs(obj.T_Stem-et));
                                set(obj.H_Stem, ...
                                    'XData', obj.Y_Stem(max(1, id-tailLength):id, 1), ...
                                    'YData', obj.Y_Stem(max(1, id-tailLength):id, 3));
                            else
                                set(obj.H_Stem, 'XData', NaN, 'YData', NaN);
                                obj.Stage = 2;
                                if obj.HaveSound
                                    try %#ok<TRYNC>
                                        play(obj.PopSound);
                                    end
                                end
                            end
                        case 2
                            et = et - obj.T_Stem(end)*obj.SpeedFactor;
                            if et < obj.T_Petals(end)
                                [minVal, id] = min(abs(obj.T_Petals-et));
                                currentTailLength = round(id/length(obj.T_Petals)*obj.TailLength);
                                sID = max(1, id-currentTailLength);
                                eID = min(length(obj.T_Petals), id);
                                idx = sID:eID;
                                set(obj.H_Petals, {'XData', 'YData'}, ...
                                    [...
                                    mat2cell(obj.Y_Petals(idx, 1:4:end)', ones(1, length(obj.H_Petals)), length(idx)), ...
                                    mat2cell(obj.Y_Petals(idx, 3:4:end)', ones(1, length(obj.H_Petals)), length(idx))]);
                            elseif et <= obj.T_Petals(end) + obj.PetalTailTime
                                pct = 1 - (et - obj.T_Petals(end))/obj.PetalTailTime;
                                sID = length(obj.T_Petals) - round(obj.TailLength*pct);
                                eID = length(obj.T_Petals);
                                idx = sID:eID;
                                c = obj.getColor(obj.PetalColorIndex, 1-pct);
                                set(obj.H_Petals, {'Color', 'XData', 'YData'}, ...
                                    [...
                                    mat2cell(c, ones(1, length(obj.H_Petals)), 3), ...
                                    mat2cell(obj.Y_Petals(idx, 1:4:end)', ones(1, length(obj.H_Petals)), length(idx)), ...
                                    mat2cell(obj.Y_Petals(idx, 3:4:end)', ones(1, length(obj.H_Petals)), length(idx))]);
                            else
                                    set(obj.H_Petals, 'XData', NaN, 'YData', NaN);
                                    obj.Stage = 3;
                            end
                    end
            end
            % drawnow;
            
        end
        
        %% Getter Functions
        function val = get.PetalColorIndex(obj)
            val = round(7*rand(1, length(obj.H_Petals)))+1;
        end
        
    end
    
    %% Methods (Hidden)
    methods (Hidden)
        function delete(obj)
            warning('off', 'MATLAB:class:DestructorError');

            if ishandle(obj.H_Stem), delete(obj.H_Stem), end
            if ishandle(obj.H_Petals), delete(obj.H_Petals), end
            
            delete@handle(obj);

            warning('on', 'MATLAB:class:DestructorError');
        end
    end
    
    methods (Access = protected, Hidden)
        
        function tf = isOperable(obj)
            tf = isvalid(obj) && ~isempty(obj.FWAxes.AxH);
        end
        
        function computeTrajectory(obj, param)
            if ~isOperable(obj)
                return;
            end
            
            if ~ishandle(obj.FWAxes.AxH)
                delete(obj);
                return;
            end
                        
            m = 1;
            b = 0;
            
            numPetals = length(param.ExplodeVelocity);
            if param.ThinLines
                lineWidth = 0.5;
            else
                lineWidth = 2;
            end

            % Initial conditions
            y0 = [param.LaunchPos, param.LaunchVel(1), 0, param.LaunchVel(2)];
            
            % Stop at the top of the flight
            opt = odeset('Events', @terminateFcn);
            [T, Y] = ode45(@(t, y) odeFcn(t, y, m, b), ...
                [0 100], y0, opt);
            
            % Re-solve with finer time steps
            [obj.T_Stem, obj.Y_Stem] = ode45(@(t, y) odeFcn(t, y, m, b), ...
                linspace(0, T(end)), y0);
                        
            obj.T_Petals = linspace(0, 12, 50)';
            obj.Y_Petals = nan(50, 4*numPetals);
            g = 2;
            
            for id = 1:numPetals
                [T2, obj.Y_Petals(:, id*4-3:id*4)] = ode45(@(t, y) odeFcn(t, y, m, b, g), ...
                    obj.T_Petals, ...
                    [obj.Y_Stem(end, 1), ...
                    param.ExplodeVelocity(id)*cos(param.ExplodeAngle(id)), ...
                    obj.Y_Stem(end, 3), ...
                    param.ExplodeVelocity(id)*sin(param.ExplodeAngle(id))]);
            end
            
            speed_breaks = [0 15 20 25 inf];
            obj.SwooshSound = loadSound(obj, 'swoosh');
            obj.PopSound = loadSound(obj, 'pop', ...
                6 - find(speed_breaks > T(end)* param.SpeedFactor, 1));
            
            % Prepare plot
            if ishandle(obj.H_Stem)
                delete(obj.H_Stem);
            end
            if ishandle(obj.H_Petals)
                delete(obj.H_Petals)
            end
            
            % Stem
            obj.H_Stem = line(obj.Y_Stem(1, 1), obj.Y_Stem(1, 3), ...
                'Parent', obj.FWAxes.AxH, ...
                'LineWidth', lineWidth, ...
                'Color', [1 1 1]);
            
            % Petals
            obj.H_Petals = nan(1, numPetals);
            c = obj.getColor(obj.PetalColorIndex, 0);
            for id = 1:numPetals
                obj.H_Petals(id) = line(nan, nan, ...
                    'Parent', obj.FWAxes.AxH, ...
                    'Color', c(id, :), ...
                    'LineWidth', lineWidth, ...
                    'MarkerSize', lineWidth, ...
                    'MarkerFaceColor', c(id, :), ...
                    'MarkerEdgeColor', c(id, :));
            end
            
        end
        
        function color = getColor(obj, id, pct)
            color = obj.ColorTable((id-1)*32+2+round(min(pct, 1)*31), :);
        end
        
        function checkFinishedFcn(obj)
            if ~isempty(obj.FinishedFcn)
                obj.FinishedFcn();
            end
        end
        
        function p = loadSound(obj, tp, interval)
            
            switch tp
                case 'swoosh'
                    p = audioplayer(obj.Sounds.swoosh, 44100);
                case 'pop'
                    p = audioplayer(obj.Sounds.pop(1:interval:end, :), 22050);
                case 'crackle'
                    crackleid = round((length(obj.Sounds.crackle)-1)*rand+1);
                    p = audioplayer(obj.Sounds.crackle{crackleid}/3, 44100);
            end
            
        end
        
    end
    
    %% Hidden Methods (overloading handle methods)
    methods (Hidden)
        
        function out = addlistener(varargin)
            out = addlistener@handle(varargin{:});
        end
        
        function notify(varargin)
            notify@handle(varargin{:})
        end
        
        function out = eq(varargin)
            out = eq@handle(varargin{:});
        end
        
        function out = ge(varargin)
            out = ge@handle(varargin{:});
        end
        
        function out = gt(varargin)
            out = gt@handle(varargin{:});
        end
        
        function out = le(varargin)
            out = le@handle(varargin{:});
        end
        
        function out = lt(varargin)
            out = lt@handle(varargin{:});
        end
        
        function out = ne(varargin)
            out = ne@handle(varargin{:});
        end
        
        function out = findprop(varargin)
            out = findprop@handle(varargin{:});
        end
        
        function findobj(varargin)
            findobj@handle(varargin{:});
        end
        
    end
    
    
end


%#ok<*ASGLU>
%#ok<*NASGU>
%#ok<*INUSL>

Contact us