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!
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()