diff --git a/compute.py b/compute.py index 453ef1f..ee9e6b9 100644 --- a/compute.py +++ b/compute.py @@ -75,6 +75,7 @@ camera_center_z = 0.0 camera_pos_x = None camera_pos_y = None camera_pos_z = None +display_amp_str = "" FIXED_MASK_X = None FIXED_MASK_Y = None FIXED_MASK_Z = None @@ -774,7 +775,7 @@ def run_from_config(config, out_dir=None): global box_color_r, box_color_g, box_color_b global use_marker, camera_keyframes_raw, camera_distance, camera_elevation, camera_azimuth global camera_center_x, camera_center_y, camera_center_z - global camera_pos_x, camera_pos_y, camera_pos_z + global camera_pos_x, camera_pos_y, camera_pos_z, display_amp_str global FIXED_MASK_X, FIXED_MASK_Y, FIXED_MASK_Z global warmup_steps, sample_start, sample_end global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH @@ -849,6 +850,13 @@ def run_from_config(config, out_dir=None): camera_pos_x = config.get("camera_pos_x", None) camera_pos_y = config.get("camera_pos_y", None) camera_pos_z = config.get("camera_pos_z", None) + _damp_cfg = config.get("display_amp", None) + if _damp_cfg is not None: + import ast as _ast_cfg + _dv = np.array(_ast_cfg.literal_eval(str(_damp_cfg).strip()), dtype=np.float64) + display_amp_str = f"[{_dv[0]},{_dv[1]},{_dv[2]}]" + else: + display_amp_str = "" # 力开关 global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH @@ -1179,6 +1187,8 @@ def run_engine(engine, input_dir, output_dir, config): _cam_extra["camera_pos_x"] = str(camera_pos_x) _cam_extra["camera_pos_y"] = str(camera_pos_y) _cam_extra["camera_pos_z"] = str(camera_pos_z) + if display_amp_str: + _cam_extra["display_amp"] = display_amp_str save_display_npz( _npz_path, _d["frames_x"], _d["frames_y"], _d["frames_z"], @@ -1779,7 +1789,8 @@ def run_simulation(save_trajectory=0): "camera_pos_y": str(camera_pos_y), "camera_pos_z": str(camera_pos_z), } if camera_pos_x is not None else {}), - "camera_keyframes": str(camera_keyframes_raw)} + "camera_keyframes": str(camera_keyframes_raw), + **({"display_amp": display_amp_str} if display_amp_str else {})} ) print(f"[compute] display.txt 已保存至: {disp_path} ({n_frames_actual} 帧)") @@ -1822,7 +1833,8 @@ def run_simulation(save_trajectory=0): "camera_center_z": str(camera_center_z), "camera_keyframes": str(camera_keyframes_raw), "number_of_frames": str(record_steps), - "number_of_particles": str(n_atoms)} + "number_of_particles": str(n_atoms), + **({"display_amp": display_amp_str} if display_amp_str else {})} save_display_npz(_npz_path, sampled_x[:n_frames_actual], sampled_y[:n_frames_actual], sampled_z[:n_frames_actual], diff --git a/draw.py b/draw.py index 2c5d08f..bff71b3 100644 --- a/draw.py +++ b/draw.py @@ -67,6 +67,20 @@ N_FRAMES = DISP_ALL_X.shape[0] NT = int(disp_data["n_total_frames"]) N_ATOMS = int(disp_data["n_total_particles"]) DT = float(h.get("DT", 0.001)) + +# 视觉位移放大:display_amp: [ax, ay, az],对偏离第0帧的位移乘以倍数 +_damp_raw = h.get("display_amp", "") +if _damp_raw.strip(): + import ast as _ast + _damp_vals = _ast.literal_eval(_damp_raw.strip()) + _damp = np.array(_damp_vals, dtype=np.float64) + if _damp.shape == (3,) and not np.allclose(_damp, 1.0): + _eq_x = DISP_ALL_X[0:1, :] # 第0帧作为平衡位置参考 + _eq_y = DISP_ALL_Y[0:1, :] + _eq_z = DISP_ALL_Z[0:1, :] + DISP_ALL_X = _eq_x + (DISP_ALL_X - _eq_x) * _damp[0] + DISP_ALL_Y = _eq_y + (DISP_ALL_Y - _eq_y) * _damp[1] + DISP_ALL_Z = _eq_z + (DISP_ALL_Z - _eq_z) * _damp[2] NSTEP = int(h.get("NSTEP", 1)) DISP_STEP = np.arange(N_FRAMES) * NSTEP DISP_T = DISP_STEP * DT @@ -119,11 +133,31 @@ box_color_b = float(h.get("box_color_b", 0.85)) info_margin = 8 axis_length = 10.0 +import math as _math_cam + +_cx = float(h.get("camera_center_x", 0.0)) +_cy = float(h.get("camera_center_y", 0.0)) +_cz = float(h.get("camera_center_z", 0.0)) + +# 若 input.txt 指定了摄像机自身坐标,则由坐标反推 distance/elevation/azimuth +if h.get("camera_pos_x") is not None: + _px = float(h["camera_pos_x"]) + _py = float(h["camera_pos_y"]) + _pz = float(h["camera_pos_z"]) + _dx, _dy, _dz = _px - _cx, _py - _cy, _pz - _cz + _dist = _math_cam.sqrt(_dx*_dx + _dy*_dy + _dz*_dz) or 1.0 + _elev = _math_cam.degrees(_math_cam.asin(max(-1.0, min(1.0, _dy / _dist)))) + _azim = _math_cam.degrees(_math_cam.atan2(_dx, _dz)) +else: + _dist = float(h.get("camera_distance", 40.0)) + _elev = float(h.get("camera_elevation", 0)) + _azim = float(h.get("camera_azimuth", 0)) + initial_camera = { - "distance": float(h.get("camera_distance", 40.0)), - "elevation": float(h.get("camera_elevation", 0)), - "azimuth": float(h.get("camera_azimuth", 0)), - "center": (0, 0, 0), + "distance": _dist, + "elevation": _elev, + "azimuth": _azim, + "center": (_cx, _cy, _cz), } @@ -595,7 +629,9 @@ def _load_move_camera_txt(): return segs if segs else None # 先试 move_camera.txt 直读,没有则用 display.txt 缓存 -_CAM_MOTION = _load_move_camera_txt() +# header 中 camera_keyframes 为空字符串表示 move_camera=0(开关关闭),跳过文件加载 +_camera_motion_enabled = bool(h.get("camera_keyframes", "")) +_CAM_MOTION = _load_move_camera_txt() if _camera_motion_enabled else None if not _CAM_MOTION: _CAM_MOTION = json.loads(h.get("camera_keyframes", "null")) if h.get("camera_keyframes") else None if _CAM_MOTION: @@ -609,14 +645,14 @@ if _CAM_MOTION: def _update_motion_camera(f_idx): """速度段驱动:每帧累加平移/旋转。 - + 时间交叠时所有段同时生效,按文件中出现的顺序依次作用。 - 矩阵操作不具有对易性,排在前面的段优先作用于相机位置。 + 只有当前帧存在活动段时才覆写相机,否则保留用户键盘/鼠标操作的结果。 """ if not _CAM_MOTION: return global _cam_center, _cam_elev, _cam_azim, _cam_dist - # 找当前帧所有活动的段(时间交叠=同时作用),按文件顺序依次应用 + active = False for seg in _CAM_MOTION: if seg["start"] <= f_idx < seg["end"]: _cam_center[0] += seg["v"][0] @@ -624,12 +660,13 @@ def _update_motion_camera(f_idx): _cam_center[2] += seg["v"][2] _cam_elev += seg["r"][0] _cam_azim += seg["r"][1] - # rz 预留 + active = True - view.camera.center = tuple(_cam_center) - view.camera.distance = _cam_dist - view.camera.elevation = _cam_elev - view.camera.azimuth = _cam_azim + if active: + view.camera.center = tuple(_cam_center) + view.camera.distance = _cam_dist + view.camera.elevation = _cam_elev + view.camera.azimuth = _cam_azim def update(event):