Description of Motion#

Consider a particle moving in 3D-Euclidean space as shown in the below image. Let’s plot the diagram and animate the motion!

a-point-in-e-frame.png

Prerequisites#

Import following libraries on your notebook

[126]:
import plotly.graph_objects as go
import numpy as np
from scipy.interpolate import CubicSpline
from scipy.spatial.distance import euclidean

Note

The functions create_line_trace, create_point_trace, create_arrow_trace, and and others were written in previous tutorials. Please include them in your notebook on top before starting to follow this tutorial. You can download it by clicking the Download icon on the Navigation Bar.

Steps#

Step 1: Define the Origin and End Points#

Here, we define the coordinates of the origin (\(O\)), the starting point (start), and the ending point (end) for the motion of the particle.

[128]:
origin = [0, 0, 0]
start = [4, 1, 1]
end = [10, 10, 10]

Step 2: Initialize an Empty List for Traces#

An empty list is initialized to store Plotly traces.

[129]:
traces = []

Step 3: Add a Random Curve and Get Curve Points#

[130]:
def create_wavy_curve_traces(start_point, end_point, num_control_points=10, waviness=1,
                             color='blue', width=2, name='Wavy Curve', seed=None):

    # Generating control points
    control_points = np.linspace(np.array(start_point), np.array(end_point), num_control_points)
    random_offsets = waviness * np.random.rand(num_control_points, 3) - waviness / 2
    control_points += random_offsets
    control_points[0, :] = start_point
    control_points[-1, :] = end_point

    # Creating cubic spline through control points
    t = np.linspace(0, 1, num_control_points)
    spline_x = CubicSpline(t, control_points[:, 0])
    spline_y = CubicSpline(t, control_points[:, 1])
    spline_z = CubicSpline(t, control_points[:, 2])

    # Generating points along the spline
    t_fine = np.linspace(0, 1, 1000)
    curve_points_x = spline_x(t_fine)
    curve_points_y = spline_y(t_fine)
    curve_points_z = spline_z(t_fine)

    # Create traces
    line_trace = go.Scatter3d(x=curve_points_x, y=curve_points_y, z=curve_points_z,
                              mode='lines',
                              line=dict(color=color, width=width),
                              name=name)
    scatter_trace = go.Scatter3d(x=control_points[:, 0], y=control_points[:, 1], z=control_points[:, 2],
                                 mode='markers',
                                 marker=dict(color=color, size=width),
                                 name=f'{name} Control Points')

    curve_points = np.column_stack((curve_points_x, curve_points_y, curve_points_z))
    return [line_trace, scatter_trace], curve_points



[131]:
curve_traces, curve_points = create_wavy_curve_traces(start, end, seed=1, waviness=3, name=r'Path of the particle')

A wavy curve is created using the create_wavy_curve_traces function, and its traces and points are obtained.

Step 4: Add Traces for Initial Particle Position and Curve#

Traces for the initial position of the particle (\(P\)) and the curve are added to the list.

[132]:
traces.append(create_point_trace(point=curve_points[0], color='orange', size=5, name='P'))
traces.append(curve_traces[0])

Step 5: Add Origin ‘O’#

A trace for the origin (\(O\)) is added to the list.

[133]:
traces.append(create_point_trace(origin, color='black', size=3, name='O'))

Step 6: Create Orthonormal Frame Traces#

Traces for the orthonormal frame (\(e\)) are created using the create_orthonormal_frame_traces function, and they are added to the list.

[134]:
frame_traces = create_orthonormal_frame_traces(frame_name='e', origin=origin, length=5, color='red')
traces.extend(frame_traces)

Step 7: Select Points on the Curve#

Points on the curve are selected based on a specified point distance.

[135]:
def select_points_on_curve(curve_points, point_distance=5):
    try:
        if not isinstance(curve_points, np.ndarray):
            raise TypeError("curve_points must be a numpy array.")
        if point_distance <= 0:
            raise ValueError("Point distance must be positive.")

        # Function to calculate arc length between two indices
        def calculate_arc_length(idx1, idx2):
            length = 0
            for i in range(idx1, idx2):
                length += euclidean(curve_points[i], curve_points[i + 1])
            return length

        # Finding two points approximately 'point_distance' apart
        idx1 = np.random.randint(0, len(curve_points) - 1)
        for idx2 in range(idx1 + 1, len(curve_points)):
            if calculate_arc_length(idx1, idx2) >= point_distance:
                return np.array([curve_points[idx1], curve_points[idx2]])

        raise ValueError("Unable to find two points with the specified distance apart on the curve.")

    except Exception as e:
        print(f"An error occurred: {e}")
        return np.array([])

[136]:
selected_points = select_points_on_curve(curve_points, point_distance=5)

Step 8: Add Points on the Curve#

Traces for the selected points on the curve are added to the list.

[137]:
traces.append(create_point_trace(point=selected_points[0], color='green', size=3, name='P(t)'))
traces.append(create_point_trace(point=selected_points[1], color='green', size=3, name='P(t+δt)'))

Step 9: Add Lines Between Points#

Traces for lines connecting points are added to represent motion and displacements.

[138]:
traces.append(create_line_trace(start=origin, end=selected_points[0], color='blue', width=3, dash='dash', name='x(t)', showlegend=True))
traces.append(create_line_trace(start=origin, end=selected_points[1], color='red', width=3, dash='dash', name='x(t+δt)', showlegend=True))
traces.append(create_line_trace(start=selected_points[0], end=selected_points[1], color='purple', width=3, dash='dash', name='x(t+δt) - x(t)', showlegend=True))

Step 10: Add Orthonormal Frame at the First Selected Point#

Traces for an orthonormal frame at the first selected point (\(P\)) are added.

[139]:
frame_traces_P = create_orthonormal_frame_traces(frame_name='P', origin=selected_points[0], length=2, color='black')
traces.extend(frame_traces_P)

Step 11: Creating Frames for the Animation#

Frames for animating the particle along the curve are created using the animate_particle function.

[140]:
frames = animate_particle(curve_points, 'P', particle_color='orange', particle_size=5, animation_speed=5)

Step 12: Set Layout for the Figure#

The layout for the figure is set using the create_3d_layout function, and the Plotly figure is created with traces and frames.

[141]:
layout = create_3d_layout(title='Point P in 3D Inertial Frame e', xaxis_title='e1 Axis', yaxis_title='e2 Axis', zaxis_title='e3 Axis')
fig = go.Figure(data=traces, layout=layout, frames=frames)

Step 13: Adjust the Camera Settings and Update Layout#

The camera settings are adjusted for better visualization.

[142]:
fig.update_layout(
    scene=dict(
        camera=dict(up=dict(x=0, y=0, z=1), center=dict(x=0, y=0, z=0), eye=dict(x=1.25, y=-1.25, z=1.25)),
        aspectmode='cube'
    )
)

# Add Play and Pause Buttons
fig.update_layout(
    updatemenus=[
        dict(
            type="buttons",
            buttons=[
                dict(label="Play",
                      method="animate",
                      args=[None, dict(frame=dict(duration=50, redraw=True), fromcurrent=True)]),
                dict(label="Pause",
                      method="animate",
                      args=[[None], dict(frame=dict(duration=0, redraw=False), mode="immediate")])
            ]
        )
    ]
)

# Display the figure
fig.show()