Created
October 29, 2025 19:00
-
-
Save yanndebray/b501fe03c08b67d0056f06afb7fcbae8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function MassSpringDamperSimulator() | |
| % Mass Spring Damper Simulator (1-DOF) | |
| % Interactive simulation with base excitation | |
| % Replicates the web-based simulator functionality | |
| % Create figure | |
| fig = figure('Name', 'Mass Spring Damper Simulator (1-DOF)', ... | |
| 'NumberTitle', 'off', ... | |
| 'Position', [100, 100, 1400, 800], ... | |
| 'Color', [0.95, 0.95, 0.98]); | |
| % Main title | |
| uicontrol('Style', 'text', ... | |
| 'String', 'Mass Spring Damper Simulator', ... | |
| 'FontSize', 24, ... | |
| 'FontWeight', 'bold', ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.25, 0.93, 0.5, 0.05], ... | |
| 'BackgroundColor', [0.95, 0.95, 0.98], ... | |
| 'HorizontalAlignment', 'center'); | |
| uicontrol('Style', 'text', ... | |
| 'String', 'Interactive 1-DOF system with base excitation. Tweak parameters and watch the animation update in real time.', ... | |
| 'FontSize', 10, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.15, 0.89, 0.7, 0.03], ... | |
| 'BackgroundColor', [0.95, 0.95, 0.98], ... | |
| 'HorizontalAlignment', 'center'); | |
| % Parameters panel | |
| paramPanel = uipanel('Title', 'Parameters', ... | |
| 'FontSize', 12, ... | |
| 'FontWeight', 'bold', ... | |
| 'BackgroundColor', [1, 1, 1], ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.02, 0.05, 0.22, 0.82]); | |
| % Visualization panel | |
| visPanel = uipanel('Title', 'Mass spring damper - 1 DOF', ... | |
| 'FontSize', 12, ... | |
| 'FontWeight', 'bold', ... | |
| 'BackgroundColor', [1, 1, 1], ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.26, 0.05, 0.72, 0.82]); | |
| % Initialize parameters | |
| params = struct(); | |
| params.mass = 1000.0; % kg | |
| params.springK = 48000.0; % N/m | |
| params.dampingC = 4000.0; % N*s/m | |
| params.amplitudeA = 0.1; % m | |
| params.frequencyF = 1.0; % Hz | |
| params.L0 = 0.7; % m | |
| params.blockH = 0.4; % m | |
| params.blockA = 0.8; % m | |
| params.timeTF = 15.0; % s | |
| params.frameRate = 30; % fps (reduced from 60 for stability) | |
| params.isPaused = false; | |
| params.time = 0; | |
| params.position = 0; | |
| params.velocity = 0; | |
| % Create sliders and labels | |
| yPos = 0.88; | |
| deltaY = 0.11; | |
| % Mass slider | |
| createSlider(paramPanel, 'Mass [kg]', 100, 5000, params.mass, ... | |
| [0.05, yPos, 0.9, 0.05], @(val) updateParam('mass', val)); | |
| yPos = yPos - deltaY; | |
| % Spring k slider | |
| createSlider(paramPanel, 'Spring k [N/m]', 1000, 100000, params.springK, ... | |
| [0.05, yPos, 0.9, 0.05], @(val) updateParam('springK', val)); | |
| yPos = yPos - deltaY; | |
| % Damping c slider | |
| createSlider(paramPanel, 'Damping c [N*s/m]', 0, 10000, params.dampingC, ... | |
| [0.05, yPos, 0.9, 0.05], @(val) updateParam('dampingC', val)); | |
| yPos = yPos - deltaY; | |
| % Amplitude A slider | |
| createSlider(paramPanel, 'Amplitude A [m]', 0, 0.5, params.amplitudeA, ... | |
| [0.05, yPos, 0.9, 0.05], @(val) updateParam('amplitudeA', val)); | |
| yPos = yPos - deltaY; | |
| % Frequency f slider | |
| createSlider(paramPanel, 'Frequency f [Hz]', 0.1, 5, params.frequencyF, ... | |
| [0.05, yPos, 0.9, 0.05], @(val) updateParam('frequencyF', val)); | |
| yPos = yPos - deltaY; | |
| % L0 and Block h sliders (side by side) | |
| createSlider(paramPanel, 'L0 [m]', 0.3, 1.5, params.L0, ... | |
| [0.05, yPos, 0.42, 0.05], @(val) updateParam('L0', val)); | |
| createSlider(paramPanel, 'Block h [m]', 0.2, 0.8, params.blockH, ... | |
| [0.53, yPos, 0.42, 0.05], @(val) updateParam('blockH', val)); | |
| yPos = yPos - deltaY; | |
| % Block a and Time TF sliders (side by side) | |
| createSlider(paramPanel, 'Block a [m]', 0.3, 1.5, params.blockA, ... | |
| [0.05, yPos, 0.42, 0.05], @(val) updateParam('blockA', val)); | |
| createSlider(paramPanel, 'Time TF [s]', 5, 30, params.timeTF, ... | |
| [0.53, yPos, 0.42, 0.05], @(val) updateParam('timeTF', val)); | |
| yPos = yPos - deltaY; | |
| % Frame rate control | |
| uicontrol('Parent', paramPanel, 'Style', 'text', ... | |
| 'String', 'Frame rate [fps]', ... | |
| 'FontSize', 10, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.05, yPos+0.015, 0.6, 0.03], ... | |
| 'BackgroundColor', [1, 1, 1], ... | |
| 'HorizontalAlignment', 'left'); | |
| uicontrol('Parent', paramPanel, 'Style', 'edit', ... | |
| 'String', num2str(params.frameRate), ... | |
| 'FontSize', 10, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.7, yPos+0.015, 0.25, 0.03], ... | |
| 'Callback', @(src, ~) updateFrameRate(str2double(src.String))); | |
| % Pause and Reset buttons | |
| pauseBtn = uicontrol('Parent', paramPanel, 'Style', 'pushbutton', ... | |
| 'String', 'Pause', ... | |
| 'FontSize', 11, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.05, 0.02, 0.42, 0.06], ... | |
| 'Callback', @togglePause); | |
| uicontrol('Parent', paramPanel, 'Style', 'pushbutton', ... | |
| 'String', 'Reset', ... | |
| 'FontSize', 11, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.53, 0.02, 0.42, 0.06], ... | |
| 'Callback', @resetSimulation); | |
| % Create axes for visualization | |
| ax = axes('Parent', visPanel, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [0.1, 0.1, 0.85, 0.85]); | |
| hold(ax, 'on'); | |
| grid(ax, 'on'); | |
| axis(ax, 'equal'); | |
| xlabel(ax, 'x [m]', 'FontSize', 11); | |
| ylabel(ax, 'y [m]', 'FontSize', 11); | |
| % Timer variable | |
| tmr = []; | |
| % Initialize simulation | |
| resetSimulation(); | |
| % Start animation timer with safer settings | |
| tmr = timer('ExecutionMode', 'fixedRate', ... | |
| 'Period', max(0.001, 1/params.frameRate), ... % Ensure minimum 1ms | |
| 'BusyMode', 'drop', ... | |
| 'TimerFcn', @updateAnimation); | |
| try | |
| start(tmr); | |
| disp('Animation started successfully'); | |
| catch ME | |
| disp(['Timer error: ' ME.message]); | |
| end | |
| % Cleanup on close | |
| fig.CloseRequestFcn = @cleanupFigure; | |
| function cleanupFigure(~,~) | |
| if ~isempty(tmr) && isvalid(tmr) | |
| stop(tmr); | |
| delete(tmr); | |
| end | |
| delete(fig); | |
| end | |
| function createSlider(parent, label, minVal, maxVal, initVal, pos, callback) | |
| % Label with value on same line | |
| labelStr = sprintf('%s: %.3f', label, initVal); | |
| valDisp = uicontrol('Parent', parent, 'Style', 'text', ... | |
| 'String', labelStr, ... | |
| 'FontSize', 9, ... | |
| 'Units', 'normalized', ... | |
| 'Position', [pos(1), pos(2)+0.055, pos(3), 0.025], ... | |
| 'BackgroundColor', [1, 1, 1], ... | |
| 'HorizontalAlignment', 'left'); | |
| % Slider | |
| uicontrol('Parent', parent, 'Style', 'slider', ... | |
| 'Min', minVal, 'Max', maxVal, ... | |
| 'Value', initVal, ... | |
| 'Units', 'normalized', ... | |
| 'Position', pos, ... | |
| 'Callback', @(src, ~) sliderCallback(src, valDisp, callback, label)); | |
| end | |
| function sliderCallback(src, valDisp, callback, label) | |
| val = get(src, 'Value'); | |
| labelStr = sprintf('%s: %.3f', label, val); | |
| set(valDisp, 'String', labelStr); | |
| callback(val); | |
| end | |
| function updateParam(paramName, value) | |
| params.(paramName) = value; | |
| end | |
| function updateFrameRate(value) | |
| if value > 0 && value <= 60 | |
| params.frameRate = value; | |
| if ~isempty(tmr) && isvalid(tmr) | |
| stop(tmr); | |
| tmr.Period = max(0.001, 1/value); | |
| start(tmr); | |
| end | |
| end | |
| end | |
| function togglePause(~, ~) | |
| params.isPaused = ~params.isPaused; | |
| if params.isPaused | |
| set(pauseBtn, 'String', 'Resume'); | |
| else | |
| set(pauseBtn, 'String', 'Pause'); | |
| end | |
| end | |
| function resetSimulation(~, ~) | |
| params.time = 0; | |
| params.position = 0; | |
| params.velocity = 0; | |
| params.isPaused = false; | |
| set(pauseBtn, 'String', 'Pause'); | |
| % Draw initial state | |
| drawSystem(); | |
| end | |
| function drawSystem() | |
| % Clear axes | |
| cla(ax); | |
| hold(ax, 'on'); | |
| grid(ax, 'on'); | |
| % Calculate positions | |
| omega = 2 * pi * params.frequencyF; | |
| baseDisp = params.amplitudeA * sin(omega * params.time); | |
| yBase = baseDisp; | |
| yMass = yBase + params.L0 + params.position; | |
| % Set axis limits dynamically | |
| xRange = 2.5; | |
| yMin = min(-0.5, yBase - 0.5); | |
| yMax = max(params.L0 + params.blockH + 1.0, yMass + params.blockH/2 + 0.5); | |
| axis(ax, [-xRange/2, xRange/2, yMin, yMax]); | |
| % Draw ground (fixed reference) | |
| groundY = -0.3; | |
| groundWidth = 2.0; | |
| plot(ax, [-groundWidth/2, groundWidth/2], [groundY, groundY], ... | |
| 'k-', 'LineWidth', 3); | |
| % Draw hatching for ground | |
| for i = -10:10 | |
| x = i * 0.1; | |
| if abs(x) <= groundWidth/2 | |
| plot(ax, [x, x-0.05], [groundY, groundY-0.1], 'k-', 'LineWidth', 1); | |
| end | |
| end | |
| % Draw base platform | |
| baseWidth = params.blockA; | |
| baseHeight = 0.15; | |
| rectangle('Parent', ax, ... | |
| 'Position', [-baseWidth/2, yBase-baseHeight/2, baseWidth, baseHeight], ... | |
| 'FaceColor', [0.7, 0.7, 0.7], ... | |
| 'EdgeColor', 'k', ... | |
| 'LineWidth', 2); | |
| % Draw spring (zigzag pattern) | |
| springCoils = 12; | |
| springWidth = 0.15; | |
| springX = -0.3; | |
| numPoints = springCoils * 2 + 1; | |
| springY = linspace(yBase + baseHeight/2, yMass - params.blockH/2, numPoints); | |
| springXCoords = zeros(1, numPoints); | |
| springXCoords(1) = springX; | |
| springXCoords(end) = springX; | |
| for i = 2:numPoints-1 | |
| if mod(i, 2) == 0 | |
| springXCoords(i) = springX + springWidth; | |
| else | |
| springXCoords(i) = springX - springWidth; | |
| end | |
| end | |
| plot(ax, springXCoords, springY, 'k-', 'LineWidth', 2); | |
| % Draw damper | |
| damperX = 0.3; | |
| totalDamperLen = yMass - params.blockH/2 - (yBase + baseHeight/2); | |
| damperPistonLen = totalDamperLen * 0.7; | |
| damperCylLen = totalDamperLen * 0.3; | |
| % Damper piston (line) | |
| plot(ax, [damperX, damperX], [yBase + baseHeight/2, yBase + baseHeight/2 + damperPistonLen], ... | |
| 'k-', 'LineWidth', 3); | |
| % Damper cylinder | |
| cylWidth = 0.15; | |
| rectangle('Parent', ax, ... | |
| 'Position', [damperX-cylWidth/2, yBase+baseHeight/2+damperPistonLen, cylWidth, damperCylLen], ... | |
| 'FaceColor', [0.6, 0.6, 0.6], ... | |
| 'EdgeColor', 'k', ... | |
| 'LineWidth', 2); | |
| % Damper connection to mass | |
| plot(ax, [damperX, damperX], ... | |
| [yBase+baseHeight/2+damperPistonLen+damperCylLen, yMass-params.blockH/2], ... | |
| 'k-', 'LineWidth', 3); | |
| % Draw mass (block) | |
| rectangle('Parent', ax, ... | |
| 'Position', [-params.blockA/2, yMass-params.blockH/2, ... | |
| params.blockA, params.blockH], ... | |
| 'FaceColor', [0.4, 0.3, 0.8], ... | |
| 'EdgeColor', 'k', ... | |
| 'LineWidth', 2); | |
| % Draw guide lines (dashed) | |
| xLim = get(ax, 'XLim'); | |
| yEquilibrium = params.L0; | |
| plot(ax, xLim, [yEquilibrium, yEquilibrium], 'k--', 'LineWidth', 1); | |
| plot(ax, xLim, [yBase, yBase], 'k--', 'LineWidth', 1); | |
| plot(ax, xLim, [yMass, yMass], 'k--', 'LineWidth', 1); | |
| drawnow; | |
| end | |
| function updateAnimation(~, ~) | |
| if params.isPaused | |
| return; | |
| end | |
| try | |
| % Update time | |
| dt = 1/params.frameRate; | |
| params.time = params.time + dt; | |
| % Reset if time exceeds simulation time | |
| if params.time >= params.timeTF | |
| params.time = 0; | |
| params.position = 0; | |
| params.velocity = 0; | |
| end | |
| % Calculate base excitation | |
| omega = 2 * pi * params.frequencyF; | |
| baseDisp = params.amplitudeA * sin(omega * params.time); | |
| baseVel = params.amplitudeA * omega * cos(omega * params.time); | |
| % Calculate relative displacement | |
| relDisp = params.position - baseDisp; | |
| relVel = params.velocity - baseVel; | |
| % Forces | |
| springForce = -params.springK * relDisp; | |
| dampingForce = -params.dampingC * relVel; | |
| % Acceleration | |
| acceleration = (springForce + dampingForce) / params.mass; | |
| % Update velocity and position (Euler integration) | |
| params.velocity = params.velocity + acceleration * dt; | |
| params.position = params.position + params.velocity * dt; | |
| % Redraw system | |
| drawSystem(); | |
| catch ME | |
| disp(['Animation error: ' ME.message]); | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment