traveling wave





import bpy
import math
# ============================================================
# 4-BONE TRAVELING Y WAVE
#
# Mix konsep:
# A = setiap bone naik/turun
# B = wave merambat sepanjang deretan bone
#
# ONLY LOCATION Y
#
# Frame 1   = current Y pose
# Frame 201 = exact frame 1
#
# Playback / render:
# 1 - 200
# ============================================================
ARMATURE_NAME = "Armature.003"
START_FRAME = 1
PERIOD = 200
LOOP_FRAME = START_FRAME + PERIOD

# ============================================================
# BONE ORDER SECARA SPASIAL
#
# Sumber/ujung kanan -> menuju ujung lainnya.
#
# Kalau nanti arah rambat terbalik,
# cukup reverse list ini atau ubah WAVE_DIRECTION.
# ============================================================
BONES = [
    "Bone.005",
    "Bone.004",
    "Bone.002",
    "Bone001",
]

# ============================================================
# CONTROLS
# ============================================================
# Besar displacement wave dari bentuk awal.
AMPLITUDE = 1.45
# Berapa banyak gelombang sepanjang 4 bone.
#
# 1.0 = satu cycle penuh dari bone pertama ke terakhir.
# 0.75 = wave lebih panjang / elegan.
# 1.25 = wave lebih rapat.
SPATIAL_CYCLES = 0.85
# +1 / -1 membalik arah rambat.
WAVE_DIRECTION = 1.0

# ============================================================
# SPARSE SAMPLE FRAMES
#
# Bukan tiap frame.
# Masih gampang diedit manual.
# ============================================================
KEY_FRAMES = [
    1,
    13,
    26,
    38,
    51,
    63,
    76,
    88,
    101,
    113,
    126,
    138,
    151,
    163,
    176,
    188,
    201,
]

# ============================================================
# GET ARMATURE
# ============================================================
arm = bpy.data.objects.get(ARMATURE_NAME)
if arm is None:
    raise Exception(
        f"Armature '{ARMATURE_NAME}' tidak ditemukan."
    )
if arm.type != 'ARMATURE':
    raise Exception(
        f"'{ARMATURE_NAME}' bukan Armature."
    )
scene = bpy.context.scene

# ============================================================
# FRAME 1 = BASE SHAPE
# ============================================================
scene.frame_set(START_FRAME)
BASE_Y = {}
for name in BONES:
    pb = arm.pose.bones.get(name)
    if pb is None:
        raise Exception(
            f"Bone '{name}' tidak ditemukan."
        )
    BASE_Y[name] = pb.location.y

# ============================================================
# BACKUP ACTION
# ============================================================
if arm.animation_data and arm.animation_data.action:
    old = arm.animation_data.action
    backup = old.copy()
    backup.name = old.name + "_BEFORE_TRAVEL_WAVE"
    backup.use_fake_user = True

# ============================================================
# CLEAR OLD ANIMATION
# ============================================================
if arm.animation_data:
    arm.animation_data_clear()

# ============================================================
# SPATIAL POSITION
#
# 4 bones:
#
# 005 = 0.00
# 004 = 0.33
# 002 = 0.67
# 001 = 1.00
# ============================================================
N = len(BONES)
X = {}
for i, name in enumerate(BONES):
    X[name] = (
        i / (N - 1)
    )

# ============================================================
# WAVE FUNCTION
#
# Traveling wave:
#
# sin(kx - wt)
# ============================================================
def wave(name, time_phase):
    x = X[name]
    spatial_phase = (
        math.tau
        * SPATIAL_CYCLES
        * x
    )
    return math.sin(
        spatial_phase
        -
        WAVE_DIRECTION * time_phase
    )

# ============================================================
# INITIAL WAVE VALUE
#
# Kita kurangi nilai ini dari seluruh animasi.
#
# Hasilnya:
# displacement pada frame 1 = 0.
# ============================================================
INITIAL_WAVE = {}
for name in BONES:
    INITIAL_WAVE[name] = wave(
        name,
        0.0
    )

# ============================================================
# GENERATE
# ============================================================
for frame in KEY_FRAMES:
    progress = (
        frame - START_FRAME
    ) / PERIOD
    time_phase = (
        math.tau * progress
    )

    for name in BONES:
        pb = arm.pose.bones[name]

        # ----------------------------------------------------
        # displacement dari traveling wave
        # ----------------------------------------------------
        current_wave = wave(
            name,
            time_phase
        )
        wave_delta = (
            current_wave
            -
            INITIAL_WAVE[name]
        )

        # ----------------------------------------------------
        # final Y
        # ----------------------------------------------------
        y = (
            BASE_Y[name]
            +
            wave_delta
            * AMPLITUDE
        )

        # Exact start/end
        if frame == START_FRAME or frame == LOOP_FRAME:
            y = BASE_Y[name]

        pb.location.y = y

        pb.keyframe_insert(
            data_path="location",
            index=1,
            frame=frame
        )

# ============================================================
# SMOOTH INTERPOLATION
#
# AUTO handles lebih flowy.
#
# Tidak pakai AUTO_CLAMPED karena di sini sedikit curvature
# antar sample justru membantu rasa gelombang.
# ============================================================
if arm.animation_data and arm.animation_data.action:
    action = arm.animation_data.action
    action.name = "TRAVELING_Y_WAVE"
    for fc in action.fcurves:
        for kp in fc.keyframe_points:
            kp.interpolation = 'BEZIER'
            kp.handle_left_type = 'AUTO'
            kp.handle_right_type = 'AUTO'

# ============================================================
# TIMELINE
# ============================================================
scene.frame_start = 1
scene.frame_end = 200
scene.frame_set(1)

# ============================================================
# REPORT
# ============================================================
print("")
print("=" * 70)
print("TRAVELING Y WAVE CREATED")
print("=" * 70)
print("")
print("BASE Y:")
for name in BONES:
    print(
        f"{name:10s}",
        round(BASE_Y[name], 4),
        "| x =",
        round(X[name], 3)
    )
print("")
print("Amplitude      :", AMPLITUDE)
print("Spatial cycles :", SPATIAL_CYCLES)
print("Period         :", PERIOD)
print("Loop reference :", LOOP_FRAME)
print("")
print("Frame 1   = original Y")
print("Frame 201 = original Y exact")
print("")
print("Only Y Location animated.")
print("=" * 70)
-----



evently
---
import bpy
import math

# ============================================================
# TRAVELING Y WAVE - EVENT BASED SPARSE KEYS
#
# ONLY LOCATION Y
#
# Frame 1   = current Y
# Frame 201 = current Y lagi
#
# Setiap bone hanya dapat key pada:
# START / MAX / CENTER / MIN / CENTER / LOOP
# ============================================================

ARMATURE_NAME = "Armature.005"

START_FRAME = 1
PERIOD = 200
LOOP_FRAME = START_FRAME + PERIOD

AMPLITUDE = 2.10

BONES = [
    "Bone.005",
    "Bone.004",
    "Bone.002",
    "Bone001",
]


# ============================================================
# SPATIAL PHASE
#
# Ini yang bikin wave "jalan" dari bone ke bone.
# ============================================================

SPATIAL_PHASE = {
    "Bone.005": math.radians(0),
    "Bone.004": math.radians(60),
    "Bone.002": math.radians(120),
    "Bone001":  math.radians(180),
}


# ============================================================
# GET ARMATURE
# ============================================================

arm = bpy.data.objects.get(ARMATURE_NAME)

if arm is None:
    raise Exception(
        f"Armature '{ARMATURE_NAME}' tidak ditemukan."
    )

scene = bpy.context.scene

scene.frame_set(START_FRAME)


# ============================================================
# READ CURRENT Y
# ============================================================

START_Y = {}

for name in BONES:

    pb = arm.pose.bones.get(name)

    if pb is None:
        raise Exception(
            f"Bone '{name}' tidak ditemukan."
        )

    START_Y[name] = pb.location.y


# ============================================================
# BACKUP
# ============================================================

if arm.animation_data and arm.animation_data.action:

    old = arm.animation_data.action

    backup = old.copy()
    backup.name = old.name + "_BEFORE_SPARSE_EVENT"
    backup.use_fake_user = True


# ============================================================
# CLEAR OLD ANIMATION
# ============================================================

if arm.animation_data:
    arm.animation_data_clear()


# ============================================================
# TIME -> FRAME
#
# satu cycle 0..2π = 200 frame
# ============================================================

def angle_to_frame(angle):

    angle %= math.tau

    return (
        START_FRAME
        +
        (angle / math.tau)
        * PERIOD
    )


# ============================================================
# CALCULATE PHASE FROM START POSITION
#
# Kita ingin:
#
# Y(0) = START_Y
#
# dengan:
#
# Y = A sin(theta + phase)
#
# lalu kita tambahkan spatial phase.
# ============================================================

PHASE = {}

for name in BONES:

    normalized = max(
        -1.0,
        min(
            1.0,
            START_Y[name] / AMPLITUDE
        )
    )

    base_phase = math.asin(normalized)

    PHASE[name] = (
        base_phase
        + SPATIAL_PHASE[name]
    ) % math.tau


# ============================================================
# EVENT ANGLES
#
# posisi sinus:
#
# +MAX   = π/2
# CENTER = π
# -MAX   = 3π/2
# CENTER = 2π
#
# lalu siklus lanjut.
# ============================================================

TARGETS = [
    ("MAX",    math.pi / 2,      AMPLITUDE),
    ("CENTER", math.pi,          0.0),
    ("MIN",    3 * math.pi / 2, -AMPLITUDE),
    ("CENTER", 2 * math.pi,      0.0),
    ("MAX",    5 * math.pi / 2,  AMPLITUDE),
]


# ============================================================
# BUILD SPARSE KEYS
# ============================================================

ALL_KEYS = {}


for name in BONES:

    phase = PHASE[name]

    pb = arm.pose.bones[name]

    keys = []


    # --------------------------------------------------------
    # START
    # --------------------------------------------------------

    keys.append(
        (
            float(START_FRAME),
            START_Y[name],
            "START"
        )
    )


    # --------------------------------------------------------
    # FIND NEXT EVENTS
    # --------------------------------------------------------

    for label, target_angle, target_y in TARGETS:

        theta = (
            target_angle
            - phase
        ) % math.tau


        # kalau persis di start, skip
        if theta < 1e-5:
            continue


        frame = angle_to_frame(
            theta
        )


        # hanya di dalam cycle
        if (
            frame > START_FRAME
            and frame < LOOP_FRAME
        ):

            keys.append(
                (
                    frame,
                    target_y,
                    label
                )
            )


    # --------------------------------------------------------
    # LOOP END
    # --------------------------------------------------------

    keys.append(
        (
            float(LOOP_FRAME),
            START_Y[name],
            "LOOP"
        )
    )


    # --------------------------------------------------------
    # SORT
    # --------------------------------------------------------

    keys.sort(
        key=lambda x: x[0]
    )


    # ========================================================
    # INSERT KEYS
    # ========================================================

    for frame, y, label in keys:

        pb.location.y = y

        pb.keyframe_insert(
            data_path="location",
            index=1,
            frame=frame
        )


    ALL_KEYS[name] = keys


# ============================================================
# BEZIER SMOOTH
# ============================================================

if arm.animation_data and arm.animation_data.action:

    action = arm.animation_data.action

    action.name = "TRAVELING_Y_WAVE_SPARSE"

    for fc in action.fcurves:

        for kp in fc.keyframe_points:

            kp.interpolation = 'BEZIER'

            kp.handle_left_type = 'AUTO_CLAMPED'
            kp.handle_right_type = 'AUTO_CLAMPED'


# ============================================================
# TIMELINE
# ============================================================

scene.frame_start = START_FRAME
scene.frame_end   = LOOP_FRAME - 1

scene.frame_set(START_FRAME)


# ============================================================
# PRINT RESULT
# ============================================================

print("")
print("=" * 75)
print("TRAVELING Y WAVE - SPARSE EVENT KEYS")
print("=" * 75)

for name in BONES:

    print("")
    print(name)

    for frame, y, label in ALL_KEYS[name]:

        print(
            f"  {label:7s}"
            f" frame {frame:7.2f}"
            f"   Y = {y: .4f}"
        )

print("")
print("Frame 201 = Frame 1")
print("=" * 75)
TRANSLATE this Page

Posting Komentar

0 Komentar