cosmol-viewer 0.1.1.dev3__tar.gz → 0.1.1.dev5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cosmol-viewer might be problematic. Click here for more details.
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/Cargo.lock +7 -5
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/Cargo.toml +1 -1
- cosmol_viewer-0.1.1.dev5/PKG-INFO +57 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/Cargo.toml +2 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/canvas.rs +22 -30
- cosmol_viewer-0.1.1.dev5/crates/core/src/shader/fragment.glsl +34 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/molecules.rs +11 -14
- cosmol_viewer-0.1.1.dev5/crates/core/src/shapes/sphere.rs +246 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/stick.rs +7 -8
- cosmol_viewer-0.1.1.dev5/crates/python/README.md +49 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/build.rs +1 -1
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/lib.rs +2 -4
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/shapes.rs +1 -1
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/pyproject.toml +4 -1
- cosmol_viewer-0.1.1.dev3/PKG-INFO +0 -5
- cosmol_viewer-0.1.1.dev3/crates/core/src/shader/fragment.glsl +0 -28
- cosmol_viewer-0.1.1.dev3/crates/core/src/shapes/sphere.rs +0 -136
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/lib.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/parser/mod.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/parser/sdf.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/scene.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/bg_fragment.glsl +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/bg_vertex.glsl +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/mod.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/vertex.glsl +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/mod.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/utils.rs +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/Cargo.toml +0 -0
- {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/parser.rs +0 -0
|
@@ -699,7 +699,7 @@ dependencies = [
|
|
|
699
699
|
|
|
700
700
|
[[package]]
|
|
701
701
|
name = "cosmol_viewer"
|
|
702
|
-
version = "0.1.1-nightly.
|
|
702
|
+
version = "0.1.1-nightly.5"
|
|
703
703
|
dependencies = [
|
|
704
704
|
"bytemuck",
|
|
705
705
|
"cosmol_viewer_core",
|
|
@@ -717,21 +717,23 @@ dependencies = [
|
|
|
717
717
|
|
|
718
718
|
[[package]]
|
|
719
719
|
name = "cosmol_viewer_core"
|
|
720
|
-
version = "0.1.1-nightly.
|
|
720
|
+
version = "0.1.1-nightly.5"
|
|
721
721
|
dependencies = [
|
|
722
722
|
"bytemuck",
|
|
723
723
|
"eframe",
|
|
724
724
|
"egui_extras",
|
|
725
725
|
"glam",
|
|
726
|
+
"once_cell",
|
|
726
727
|
"serde",
|
|
727
728
|
"serde_json",
|
|
729
|
+
"serde_repr",
|
|
728
730
|
"wasm-bindgen-futures",
|
|
729
731
|
"web-sys",
|
|
730
732
|
]
|
|
731
733
|
|
|
732
734
|
[[package]]
|
|
733
735
|
name = "cosmol_viewer_gui"
|
|
734
|
-
version = "0.1.1-nightly.
|
|
736
|
+
version = "0.1.1-nightly.5"
|
|
735
737
|
dependencies = [
|
|
736
738
|
"bytemuck",
|
|
737
739
|
"cosmol_viewer_core",
|
|
@@ -765,7 +767,7 @@ dependencies = [
|
|
|
765
767
|
|
|
766
768
|
[[package]]
|
|
767
769
|
name = "cosmol_viewer_wasm"
|
|
768
|
-
version = "0.1.1-nightly.
|
|
770
|
+
version = "0.1.1-nightly.5"
|
|
769
771
|
dependencies = [
|
|
770
772
|
"cosmol_viewer_core",
|
|
771
773
|
"eframe",
|
|
@@ -3160,7 +3162,7 @@ dependencies = [
|
|
|
3160
3162
|
|
|
3161
3163
|
[[package]]
|
|
3162
3164
|
name = "test"
|
|
3163
|
-
version = "0.1.1-nightly.
|
|
3165
|
+
version = "0.1.1-nightly.5"
|
|
3164
3166
|
dependencies = [
|
|
3165
3167
|
"cosmol_viewer",
|
|
3166
3168
|
"cosmol_viewer_core",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosmol-viewer
|
|
3
|
+
Version: 0.1.1.dev5
|
|
4
|
+
Summary: Molecular visualization tools
|
|
5
|
+
Author-email: 95028 <wjt@cosmol.org>
|
|
6
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
7
|
+
Project-URL: Repository, https://github.com/COSMol-repl/COSMol-viewer
|
|
8
|
+
|
|
9
|
+
# COSMol-viewer
|
|
10
|
+
|
|
11
|
+
A high-performance molecular visualization library built with Rust and WebGPU, designed for seamless integration into Python workflows.
|
|
12
|
+
|
|
13
|
+
- ⚡ Fast: Native-speed rendering powered by Rust and GPU acceleration
|
|
14
|
+
|
|
15
|
+
- 🧬 Flexible: Load molecules from .sdf, .pdb, and dynamically update 3D structures
|
|
16
|
+
|
|
17
|
+
- 📓 Notebook-friendly: Fully supports Jupyter and Google Colab — ideal for education, research, and live demos
|
|
18
|
+
|
|
19
|
+
- 🔁 Real-time updates: Update molecular coordinates on-the-fly for simulations or animations
|
|
20
|
+
|
|
21
|
+
- 🎨 Customizable: Control styles, camera, and rendering settings programmatically
|
|
22
|
+
|
|
23
|
+
# Installation
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install cosmol-viewer==0.1.1.dev5
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
# Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
33
|
+
|
|
34
|
+
# === Step 1: Load and render a molecule ===
|
|
35
|
+
with open("molecule.sdf", "r") as f:
|
|
36
|
+
sdf = f.read()
|
|
37
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
38
|
+
|
|
39
|
+
scene = Scene()
|
|
40
|
+
scene.scale(0.1)
|
|
41
|
+
scene.add_shape(mol, "mol")
|
|
42
|
+
|
|
43
|
+
viewer = Viewer.render(scene) # Launch the viewer
|
|
44
|
+
|
|
45
|
+
# === Step 2: Update the same molecule dynamically ===
|
|
46
|
+
import time
|
|
47
|
+
|
|
48
|
+
for i in range(1, 10): # Simulate multiple frames
|
|
49
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
50
|
+
sdf = f.read()
|
|
51
|
+
updated_mol = Molecules(parse_sdf(sdf)).centered()
|
|
52
|
+
|
|
53
|
+
scene.update_shape("mol", updated_mol)
|
|
54
|
+
viewer.update(scene)
|
|
55
|
+
|
|
56
|
+
time.sleep(0.033) # ~30 FPS
|
|
57
|
+
```
|
|
@@ -39,7 +39,6 @@ impl Canvas {
|
|
|
39
39
|
// 正值表示向上滚动,通常是“缩小”,负值是放大
|
|
40
40
|
if scroll_delta != 0.0 {
|
|
41
41
|
self.camera_state.scale *= (1.0 + scroll_delta * 0.001).clamp(0.1, 10.0);
|
|
42
|
-
println!("scale {:?}", self.camera_state.scale);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
self.camera_state = rotate_camera(self.camera_state, response.drag_motion());
|
|
@@ -289,15 +288,13 @@ impl Shader {
|
|
|
289
288
|
for mesh in scene_data._get_meshes() {
|
|
290
289
|
self.vertex3d
|
|
291
290
|
.extend(mesh.vertices.iter().enumerate().map(|(i, pos)| {
|
|
292
|
-
let mut j = i;
|
|
293
|
-
j = i % 50;
|
|
294
291
|
Vertex3d {
|
|
295
292
|
position: *pos,
|
|
296
293
|
normal: mesh.normals[i],
|
|
297
294
|
color: mesh
|
|
298
295
|
.colors
|
|
299
296
|
.as_ref()
|
|
300
|
-
.and_then(|colors| colors.get(
|
|
297
|
+
.and_then(|colors| colors.get(i))
|
|
301
298
|
.unwrap_or(&[1.0, 1.0, 1.0, 1.0])
|
|
302
299
|
.clone(),
|
|
303
300
|
}
|
|
@@ -311,14 +308,6 @@ impl Shader {
|
|
|
311
308
|
self.dirty = true;
|
|
312
309
|
}
|
|
313
310
|
|
|
314
|
-
fn destroy(&self, gl: &glow::Context) {
|
|
315
|
-
// use glow::HasContext as _;
|
|
316
|
-
// unsafe {
|
|
317
|
-
// gl.delete_program(self.program);
|
|
318
|
-
// gl.delete_vertex_array(self.vertex_array);
|
|
319
|
-
// }
|
|
320
|
-
}
|
|
321
|
-
|
|
322
311
|
fn paint(&mut self, gl: &glow::Context, aspect_ratio: f32, camera_state: CameraState) {
|
|
323
312
|
use glow::HasContext as _;
|
|
324
313
|
|
|
@@ -336,7 +325,7 @@ impl Shader {
|
|
|
336
325
|
let light = Light {
|
|
337
326
|
position: [2.0, -3.0, 2.0],
|
|
338
327
|
color: [1.0, 0.9, 0.9],
|
|
339
|
-
intensity: 0
|
|
328
|
+
intensity: 1.0,
|
|
340
329
|
};
|
|
341
330
|
|
|
342
331
|
unsafe {
|
|
@@ -362,7 +351,7 @@ impl Shader {
|
|
|
362
351
|
|
|
363
352
|
// === 绘制场景 ===
|
|
364
353
|
gl.enable(glow::DEPTH_TEST);
|
|
365
|
-
|
|
354
|
+
gl.depth_mask(true); // ✅ 关键:恢复写入深度缓冲区
|
|
366
355
|
|
|
367
356
|
// gl.enable(glow::BLEND);
|
|
368
357
|
// gl.blend_func_separate(
|
|
@@ -437,7 +426,7 @@ impl Shader {
|
|
|
437
426
|
gl.uniform_3_f32_slice(
|
|
438
427
|
gl.get_uniform_location(self.program, "u_light_color")
|
|
439
428
|
.as_ref(),
|
|
440
|
-
(light.color).as_ref(),
|
|
429
|
+
(light.color.map(|x| x * light.intensity)).as_ref(),
|
|
441
430
|
);
|
|
442
431
|
|
|
443
432
|
gl.uniform_1_f32(
|
|
@@ -497,34 +486,37 @@ impl CameraState {
|
|
|
497
486
|
|
|
498
487
|
pub fn rotate_camera(mut camera_state: CameraState, drag_motion: Vec2) -> CameraState {
|
|
499
488
|
let sensitivity = 0.005;
|
|
500
|
-
let yaw = drag_motion.x * sensitivity;
|
|
501
|
-
let pitch = drag_motion.y * sensitivity;
|
|
489
|
+
let yaw = -drag_motion.x * sensitivity; // 水平拖动 → 绕 up 旋转
|
|
490
|
+
let pitch = -drag_motion.y * sensitivity; // 垂直拖动 → 绕 right 旋转
|
|
502
491
|
|
|
503
|
-
//
|
|
504
|
-
let
|
|
492
|
+
// 当前方向
|
|
493
|
+
let dir = camera_state.direction;
|
|
505
494
|
|
|
506
|
-
//
|
|
507
|
-
let
|
|
495
|
+
// right = 当前方向 × 当前 up
|
|
496
|
+
let right = dir.cross(camera_state.up).normalize();
|
|
508
497
|
|
|
509
|
-
//
|
|
510
|
-
let pitch_quat = Quat::from_axis_angle(right,
|
|
498
|
+
// 1. pitch:绕当前 right 轴旋转(垂直)
|
|
499
|
+
let pitch_quat = Quat::from_axis_angle(right, pitch);
|
|
500
|
+
let rotated_dir = pitch_quat * dir;
|
|
501
|
+
let rotated_up = pitch_quat * camera_state.up;
|
|
511
502
|
|
|
512
|
-
//
|
|
513
|
-
let
|
|
514
|
-
|
|
503
|
+
// 2. yaw:绕当前“视角 up”旋转(水平)
|
|
504
|
+
let yaw_quat = Quat::from_axis_angle(rotated_up, yaw);
|
|
505
|
+
let final_dir = yaw_quat * rotated_dir;
|
|
515
506
|
|
|
516
|
-
|
|
517
|
-
camera_state.up = yaw_quat *
|
|
507
|
+
camera_state.direction = final_dir.normalize();
|
|
508
|
+
camera_state.up = (yaw_quat * rotated_up).normalize();
|
|
518
509
|
|
|
519
|
-
|
|
510
|
+
camera_state
|
|
520
511
|
}
|
|
521
512
|
|
|
513
|
+
|
|
522
514
|
#[repr(C)]
|
|
523
515
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Debug, Serialize, Deserialize)]
|
|
524
516
|
pub struct Vertex3d {
|
|
525
517
|
pub position: [f32; 3],
|
|
526
518
|
pub normal: [f32; 3],
|
|
527
|
-
pub color: [f32; 4],
|
|
519
|
+
pub color: [f32; 4],
|
|
528
520
|
}
|
|
529
521
|
|
|
530
522
|
#[repr(C)]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
precision mediump float;
|
|
2
|
+
|
|
3
|
+
uniform vec3 u_light_pos;
|
|
4
|
+
uniform vec3 u_light_color;
|
|
5
|
+
uniform vec3 u_view_pos;
|
|
6
|
+
uniform float u_light_intensity;
|
|
7
|
+
|
|
8
|
+
in vec3 v_normal;
|
|
9
|
+
in vec4 v_color;
|
|
10
|
+
in vec3 v_frag_pos;
|
|
11
|
+
|
|
12
|
+
out vec4 FragColor;
|
|
13
|
+
|
|
14
|
+
void main() {
|
|
15
|
+
vec3 normal = normalize(v_normal);
|
|
16
|
+
vec3 light_dir = normalize(u_light_pos - v_frag_pos);
|
|
17
|
+
vec3 view_dir = normalize(u_view_pos - v_frag_pos);
|
|
18
|
+
|
|
19
|
+
// === 环境光 ===
|
|
20
|
+
vec3 ambient = 0.7 * u_light_color;
|
|
21
|
+
|
|
22
|
+
// === 漫反射 ===
|
|
23
|
+
float diff = max(dot(normal, light_dir), 0.0);
|
|
24
|
+
vec3 diffuse = 0.5 * diff * u_light_color;
|
|
25
|
+
|
|
26
|
+
// === 高光 ===
|
|
27
|
+
vec3 halfway_dir = normalize(light_dir + view_dir); // Blinn 模型
|
|
28
|
+
float spec = pow(max(dot(normal, halfway_dir), 0.0), 64.0); // shininess 可调
|
|
29
|
+
vec3 specular = 0.3 * spec * u_light_color; // 强度可调
|
|
30
|
+
|
|
31
|
+
// === 最终颜色 ===
|
|
32
|
+
vec3 lighting = (ambient + diffuse) * v_color.rgb + specular;
|
|
33
|
+
FragColor = vec4(lighting * u_light_intensity, v_color.a);
|
|
34
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
use glam::Mat3;
|
|
2
1
|
use serde::{Deserialize, Serialize};
|
|
2
|
+
use serde_repr::{Deserialize_repr, Serialize_repr};
|
|
3
3
|
|
|
4
4
|
use crate::{
|
|
5
5
|
Shape,
|
|
6
6
|
parser::sdf::MoleculeData,
|
|
7
|
-
scene::Scene,
|
|
8
7
|
shapes::{sphere::Sphere, stick::Stick},
|
|
9
8
|
utils::{Interaction, MeshData, VisualShape, VisualStyle},
|
|
10
9
|
};
|
|
@@ -50,7 +49,7 @@ impl AtomType {
|
|
|
50
49
|
pub fn color(&self) -> [f32; 3] {
|
|
51
50
|
match self {
|
|
52
51
|
AtomType::H => [1.0, 1.0, 1.0], // 白色
|
|
53
|
-
AtomType::C => [0.
|
|
52
|
+
AtomType::C => [0.3, 0.3, 0.3], // 深灰
|
|
54
53
|
AtomType::N => [0.0, 0.0, 1.0], // 蓝色
|
|
55
54
|
AtomType::O => [1.0, 0.0, 0.0], // 红色
|
|
56
55
|
AtomType::F => [0.0, 0.8, 0.0], // 绿
|
|
@@ -80,12 +79,13 @@ impl AtomType {
|
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash,
|
|
82
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
|
|
83
|
+
#[repr(u8)]
|
|
84
84
|
pub enum BondType {
|
|
85
|
-
SINGLE,
|
|
86
|
-
DOUBLE,
|
|
87
|
-
TRIPLE,
|
|
88
|
-
AROMATIC,
|
|
85
|
+
SINGLE = 1,
|
|
86
|
+
DOUBLE = 2,
|
|
87
|
+
TRIPLE = 3,
|
|
88
|
+
AROMATIC = 0,
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
@@ -210,10 +210,7 @@ impl Molecules {
|
|
|
210
210
|
let radius = self.atom_types.get(i).unwrap_or(&AtomType::Unknown).radius() * 0.2;
|
|
211
211
|
let color = self.atom_types.get(i).unwrap_or(&AtomType::Unknown).color();
|
|
212
212
|
|
|
213
|
-
println!("atom: {}, color: {:?}, radius: {}", i, color, radius);
|
|
214
|
-
|
|
215
213
|
let mut sphere = Sphere::new(*pos, radius);
|
|
216
|
-
sphere.style = self.style; // 继承样式
|
|
217
214
|
sphere.interaction = self.interaction;
|
|
218
215
|
sphere = sphere.color(color);
|
|
219
216
|
|
|
@@ -226,7 +223,7 @@ impl Molecules {
|
|
|
226
223
|
for n in mesh.normals {
|
|
227
224
|
normals.push(n.map(|x| x * scale));
|
|
228
225
|
}
|
|
229
|
-
for c in mesh.colors.
|
|
226
|
+
for c in mesh.colors.unwrap() {
|
|
230
227
|
colors.push(c);
|
|
231
228
|
}
|
|
232
229
|
for idx in mesh.indices {
|
|
@@ -243,8 +240,8 @@ impl Molecules {
|
|
|
243
240
|
let pos_b = self.atoms[b as usize];
|
|
244
241
|
|
|
245
242
|
let mut stick = Stick::new(pos_a, pos_b, 0.1); // or radius by bond type
|
|
246
|
-
stick.style = self.style;
|
|
247
243
|
stick.interaction = self.interaction;
|
|
244
|
+
stick = stick.color([0.7, 0.7, 0.7]);
|
|
248
245
|
|
|
249
246
|
let mesh = stick.to_mesh(1.0);
|
|
250
247
|
|
|
@@ -254,7 +251,7 @@ impl Molecules {
|
|
|
254
251
|
for n in mesh.normals {
|
|
255
252
|
normals.push(n.map(|x| x * scale));
|
|
256
253
|
}
|
|
257
|
-
for c in mesh.colors.
|
|
254
|
+
for c in mesh.colors.unwrap() {
|
|
258
255
|
colors.push(c);
|
|
259
256
|
}
|
|
260
257
|
for idx in mesh.indices {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
|
|
3
|
+
use crate::{scene::Scene, utils::{Interaction, MeshData, VisualShape, VisualStyle}, Shape};
|
|
4
|
+
|
|
5
|
+
use once_cell::sync::Lazy;
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use std::sync::Mutex;
|
|
8
|
+
|
|
9
|
+
#[derive(Clone)]
|
|
10
|
+
struct SphereTemplate {
|
|
11
|
+
vertices: Vec<[f32; 3]>,
|
|
12
|
+
normals: Vec<[f32; 3]>,
|
|
13
|
+
indices: Vec<u32>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static SPHERE_TEMPLATE_CACHE: Lazy<Mutex<HashMap<u32, SphereTemplate>>> = Lazy::new(|| {
|
|
17
|
+
Mutex::new(HashMap::new())
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
fn get_or_generate_template(quality: u32) -> SphereTemplate {
|
|
21
|
+
let mut cache = SPHERE_TEMPLATE_CACHE.lock().unwrap();
|
|
22
|
+
|
|
23
|
+
if let Some(template) = cache.get(&quality) {
|
|
24
|
+
return template.clone(); // 直接返回已有的
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let lat_segments = 10 * quality;
|
|
28
|
+
let lon_segments = 20 * quality;
|
|
29
|
+
|
|
30
|
+
let mut vertices = Vec::new();
|
|
31
|
+
let mut normals = Vec::new();
|
|
32
|
+
let mut indices = Vec::new();
|
|
33
|
+
|
|
34
|
+
for i in 0..=lat_segments {
|
|
35
|
+
let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
|
|
36
|
+
let sin_theta = theta.sin();
|
|
37
|
+
let cos_theta = theta.cos();
|
|
38
|
+
|
|
39
|
+
for j in 0..=lon_segments {
|
|
40
|
+
let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
|
|
41
|
+
let sin_phi = phi.sin();
|
|
42
|
+
let cos_phi = phi.cos();
|
|
43
|
+
|
|
44
|
+
let nx = sin_theta * cos_phi;
|
|
45
|
+
let ny = cos_theta;
|
|
46
|
+
let nz = sin_theta * sin_phi;
|
|
47
|
+
|
|
48
|
+
vertices.push([nx, ny, nz]); // 单位球
|
|
49
|
+
normals.push([nx, ny, nz]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for i in 0..lat_segments {
|
|
54
|
+
for j in 0..lon_segments {
|
|
55
|
+
let first = i * (lon_segments + 1) + j;
|
|
56
|
+
let second = first + lon_segments + 1;
|
|
57
|
+
|
|
58
|
+
indices.push(first);
|
|
59
|
+
indices.push(first + 1);
|
|
60
|
+
indices.push(second);
|
|
61
|
+
|
|
62
|
+
indices.push(second);
|
|
63
|
+
indices.push(first + 1);
|
|
64
|
+
indices.push(second + 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let template = SphereTemplate {
|
|
69
|
+
vertices,
|
|
70
|
+
normals,
|
|
71
|
+
indices,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
cache.insert(quality, template.clone());
|
|
75
|
+
|
|
76
|
+
template
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
|
81
|
+
pub struct Sphere {
|
|
82
|
+
pub center: [f32; 3],
|
|
83
|
+
pub radius: f32,
|
|
84
|
+
pub quality: u32,
|
|
85
|
+
|
|
86
|
+
pub style: VisualStyle,
|
|
87
|
+
pub interaction: Interaction,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl Into<Shape> for Sphere {
|
|
91
|
+
fn into(self) -> Shape {
|
|
92
|
+
Shape::Sphere(self)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl Sphere {
|
|
97
|
+
pub fn new(center: [f32; 3], radius: f32) -> Self {
|
|
98
|
+
Self {
|
|
99
|
+
center,
|
|
100
|
+
radius,
|
|
101
|
+
quality: 2,
|
|
102
|
+
style: VisualStyle {
|
|
103
|
+
opacity: 1.0,
|
|
104
|
+
visible: true,
|
|
105
|
+
..Default::default()
|
|
106
|
+
},
|
|
107
|
+
interaction: Default::default(),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pub fn center(mut self, center: [f32; 3])-> Self {
|
|
112
|
+
self.center = center;
|
|
113
|
+
self
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub fn set_radius(mut self, radius: f32)-> Self {
|
|
117
|
+
self.radius = radius;
|
|
118
|
+
self
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub fn clickable(mut self, val: bool) -> Self {
|
|
122
|
+
self.interaction.clickable = val;
|
|
123
|
+
self
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// pub fn to_mesh(&self, scale: f32) -> MeshData {
|
|
127
|
+
// let mut vertices = Vec::new();
|
|
128
|
+
// let mut normals = Vec::new();
|
|
129
|
+
// let mut indices = Vec::new();
|
|
130
|
+
// let mut colors = Vec::new();
|
|
131
|
+
|
|
132
|
+
// let lat_segments = 10 * self.quality;
|
|
133
|
+
// let lon_segments = 20 * self.quality;
|
|
134
|
+
|
|
135
|
+
// let r = self.radius;
|
|
136
|
+
// let [cx, cy, cz] = self.center;
|
|
137
|
+
|
|
138
|
+
// // 基础颜色(带透明度)
|
|
139
|
+
// let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
|
|
140
|
+
// let alpha = self.style.opacity.clamp(0.0, 1.0);
|
|
141
|
+
// let color_rgba = [base_color[0], base_color[1], base_color[2], alpha];
|
|
142
|
+
|
|
143
|
+
// for i in 0..=lat_segments {
|
|
144
|
+
// let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
|
|
145
|
+
// let sin_theta = theta.sin();
|
|
146
|
+
// let cos_theta = theta.cos();
|
|
147
|
+
|
|
148
|
+
// for j in 0..=lon_segments {
|
|
149
|
+
// let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
|
|
150
|
+
// let sin_phi = phi.sin();
|
|
151
|
+
// let cos_phi = phi.cos();
|
|
152
|
+
|
|
153
|
+
// let nx = sin_theta * cos_phi;
|
|
154
|
+
// let ny = cos_theta;
|
|
155
|
+
// let nz = sin_theta * sin_phi;
|
|
156
|
+
|
|
157
|
+
// let x = cx + r * nx;
|
|
158
|
+
// let y = cy + r * ny;
|
|
159
|
+
// let z = cz + r * nz;
|
|
160
|
+
|
|
161
|
+
// vertices.push([x, y, z].map(|x| x * scale));
|
|
162
|
+
// normals.push([nx, ny, nz].map(|x| x * scale));
|
|
163
|
+
// colors.push(color_rgba); // 每个顶点同样颜色
|
|
164
|
+
// }
|
|
165
|
+
// }
|
|
166
|
+
|
|
167
|
+
// for i in 0..lat_segments {
|
|
168
|
+
// for j in 0..lon_segments {
|
|
169
|
+
// let first = i * (lon_segments + 1) + j;
|
|
170
|
+
// let second = first + lon_segments + 1;
|
|
171
|
+
|
|
172
|
+
// indices.push(first);
|
|
173
|
+
// indices.push(first + 1);
|
|
174
|
+
// indices.push(second);
|
|
175
|
+
|
|
176
|
+
// indices.push(second);
|
|
177
|
+
// indices.push(first + 1);
|
|
178
|
+
// indices.push(second + 1);
|
|
179
|
+
// }
|
|
180
|
+
// }
|
|
181
|
+
|
|
182
|
+
// MeshData {
|
|
183
|
+
// vertices,
|
|
184
|
+
// normals,
|
|
185
|
+
// indices,
|
|
186
|
+
// colors: Some(colors),
|
|
187
|
+
// transform: None,
|
|
188
|
+
// is_wireframe: self.style.wireframe,
|
|
189
|
+
// }
|
|
190
|
+
// }
|
|
191
|
+
pub fn to_mesh(&self, scale: f32) -> MeshData {
|
|
192
|
+
let template = get_or_generate_template(self.quality);
|
|
193
|
+
|
|
194
|
+
let [cx, cy, cz] = self.center;
|
|
195
|
+
let r = self.radius;
|
|
196
|
+
|
|
197
|
+
let transformed_vertices: Vec<[f32; 3]> = template.vertices.iter()
|
|
198
|
+
.map(|v| [
|
|
199
|
+
(v[0] * r + cx) * scale,
|
|
200
|
+
(v[1] * r + cy) * scale,
|
|
201
|
+
(v[2] * r + cz) * scale,
|
|
202
|
+
])
|
|
203
|
+
.collect();
|
|
204
|
+
|
|
205
|
+
let transformed_normals: Vec<[f32; 3]> = template.normals
|
|
206
|
+
.iter()
|
|
207
|
+
.map(|n| n.map(|x| x * scale)) // 你可以不乘 scale,如果只用于方向
|
|
208
|
+
.collect();
|
|
209
|
+
|
|
210
|
+
let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
|
|
211
|
+
let alpha = self.style.opacity.clamp(0.0, 1.0);
|
|
212
|
+
let color = [base_color[0], base_color[1], base_color[2], alpha];
|
|
213
|
+
|
|
214
|
+
let colors = vec![color; transformed_vertices.len()];
|
|
215
|
+
|
|
216
|
+
MeshData {
|
|
217
|
+
vertices: transformed_vertices,
|
|
218
|
+
normals: transformed_normals,
|
|
219
|
+
indices: template.indices.clone(),
|
|
220
|
+
colors: Some(colors),
|
|
221
|
+
transform: None,
|
|
222
|
+
is_wireframe: self.style.wireframe,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
impl VisualShape for Sphere {
|
|
229
|
+
fn style_mut(&mut self) -> &mut VisualStyle {
|
|
230
|
+
&mut self.style
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
pub trait UpdateSphere {
|
|
235
|
+
fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
impl UpdateSphere for Scene {
|
|
239
|
+
fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere)) {
|
|
240
|
+
if let Some(Shape::Sphere(sphere)) = self.named_shapes.get_mut(id) {
|
|
241
|
+
f(sphere);
|
|
242
|
+
} else {
|
|
243
|
+
panic!("Sphere with ID '{}' not found or is not a Sphere", id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
use glam::Mat3;
|
|
2
1
|
use serde::{Deserialize, Serialize};
|
|
3
2
|
|
|
4
3
|
use crate::{
|
|
@@ -40,17 +39,17 @@ impl Stick {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
pub fn radius(mut self, radius: f32) -> Self{
|
|
42
|
+
pub fn radius(mut self, radius: f32) -> Self {
|
|
44
43
|
self.radius = radius;
|
|
45
44
|
self
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
pub fn start(mut self, start: [f32; 3]) -> Self{
|
|
47
|
+
pub fn start(mut self, start: [f32; 3]) -> Self {
|
|
49
48
|
self.start = start;
|
|
50
49
|
self
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
pub fn end(mut self, end: [f32; 3]) -> Self{
|
|
52
|
+
pub fn end(mut self, end: [f32; 3]) -> Self {
|
|
54
53
|
self.end = end;
|
|
55
54
|
self
|
|
56
55
|
}
|
|
@@ -96,13 +95,13 @@ impl Stick {
|
|
|
96
95
|
|
|
97
96
|
for i in 0..segments {
|
|
98
97
|
let idx = i * 2;
|
|
99
|
-
indices.push(idx);
|
|
100
|
-
indices.push(idx + 1);
|
|
101
98
|
indices.push(idx + 2);
|
|
102
|
-
|
|
103
99
|
indices.push(idx + 1);
|
|
104
|
-
indices.push(idx
|
|
100
|
+
indices.push(idx);
|
|
101
|
+
|
|
105
102
|
indices.push(idx + 2);
|
|
103
|
+
indices.push(idx + 3);
|
|
104
|
+
indices.push(idx + 1);
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
// 对齐旋转:Z -> axis
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# COSMol-viewer
|
|
2
|
+
|
|
3
|
+
A high-performance molecular visualization library built with Rust and WebGPU, designed for seamless integration into Python workflows.
|
|
4
|
+
|
|
5
|
+
- ⚡ Fast: Native-speed rendering powered by Rust and GPU acceleration
|
|
6
|
+
|
|
7
|
+
- 🧬 Flexible: Load molecules from .sdf, .pdb, and dynamically update 3D structures
|
|
8
|
+
|
|
9
|
+
- 📓 Notebook-friendly: Fully supports Jupyter and Google Colab — ideal for education, research, and live demos
|
|
10
|
+
|
|
11
|
+
- 🔁 Real-time updates: Update molecular coordinates on-the-fly for simulations or animations
|
|
12
|
+
|
|
13
|
+
- 🎨 Customizable: Control styles, camera, and rendering settings programmatically
|
|
14
|
+
|
|
15
|
+
# Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pip install cosmol-viewer==0.1.1.dev5
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Usage
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
25
|
+
|
|
26
|
+
# === Step 1: Load and render a molecule ===
|
|
27
|
+
with open("molecule.sdf", "r") as f:
|
|
28
|
+
sdf = f.read()
|
|
29
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
30
|
+
|
|
31
|
+
scene = Scene()
|
|
32
|
+
scene.scale(0.1)
|
|
33
|
+
scene.add_shape(mol, "mol")
|
|
34
|
+
|
|
35
|
+
viewer = Viewer.render(scene) # Launch the viewer
|
|
36
|
+
|
|
37
|
+
# === Step 2: Update the same molecule dynamically ===
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
for i in range(1, 10): # Simulate multiple frames
|
|
41
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
42
|
+
sdf = f.read()
|
|
43
|
+
updated_mol = Molecules(parse_sdf(sdf)).centered()
|
|
44
|
+
|
|
45
|
+
scene.update_shape("mol", updated_mol)
|
|
46
|
+
viewer.update(scene)
|
|
47
|
+
|
|
48
|
+
time.sleep(0.033) # ~30 FPS
|
|
49
|
+
```
|
|
@@ -18,8 +18,8 @@ pub struct Scene {
|
|
|
18
18
|
|
|
19
19
|
#[pymethods]
|
|
20
20
|
impl Scene {
|
|
21
|
-
#[
|
|
22
|
-
pub fn
|
|
21
|
+
#[new]
|
|
22
|
+
pub fn new() -> Self {
|
|
23
23
|
Self {
|
|
24
24
|
inner: _Scene::new(),
|
|
25
25
|
}
|
|
@@ -138,8 +138,6 @@ impl Viewer {
|
|
|
138
138
|
|
|
139
139
|
#[staticmethod]
|
|
140
140
|
pub fn render(scene: &Scene, py: Python) -> Self {
|
|
141
|
-
println!("scene {}", serde_json::to_string(&scene.inner).unwrap());
|
|
142
|
-
|
|
143
141
|
let env_type = detect_runtime_env(py).unwrap();
|
|
144
142
|
match env_type {
|
|
145
143
|
RuntimeEnv::Colab | RuntimeEnv::Jupyter => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cosmol-viewer"
|
|
3
|
-
version = "0.1.1.
|
|
3
|
+
version = "0.1.1.dev5"
|
|
4
4
|
description = "Molecular visualization tools"
|
|
5
5
|
authors = [{name = "95028", email = "wjt@cosmol.org"}]
|
|
6
6
|
|
|
@@ -11,3 +11,6 @@ build-backend = "maturin"
|
|
|
11
11
|
[tool.maturin]
|
|
12
12
|
name = "cosmol_viewer"
|
|
13
13
|
manifest-path = "crates/python/Cargo.toml"
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Repository = "https://github.com/COSMol-repl/COSMol-viewer"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
precision mediump float;
|
|
2
|
-
|
|
3
|
-
uniform vec3 u_light_pos;
|
|
4
|
-
uniform vec3 u_light_color;
|
|
5
|
-
uniform vec3 u_view_pos;
|
|
6
|
-
uniform float u_light_intensity;
|
|
7
|
-
|
|
8
|
-
in vec3 v_normal;
|
|
9
|
-
in vec4 v_color;
|
|
10
|
-
in vec3 v_frag_pos;
|
|
11
|
-
|
|
12
|
-
out vec4 FragColor;
|
|
13
|
-
void main() {
|
|
14
|
-
vec3 normal = normalize(v_normal); // 归一化法线
|
|
15
|
-
vec3 light_dir = normalize(u_light_pos - v_frag_pos); // 计算从片元到光源的方向
|
|
16
|
-
vec3 view_dir = normalize(u_view_pos - v_frag_pos); // 计算从片元到相机的方向
|
|
17
|
-
vec3 halfway_dir = normalize(light_dir + view_dir); // halfway 向量用于 Blinn-Phong 高光
|
|
18
|
-
|
|
19
|
-
float diff = max(dot(normal, light_dir), 0.0); // 漫反射项(Lambert)
|
|
20
|
-
float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0); // 高光项,32 是 shininess 参数
|
|
21
|
-
|
|
22
|
-
vec3 ambient = 0.3 * u_light_color; // 环境光,固定系数0.2
|
|
23
|
-
vec3 diffuse = diff * u_light_color; // 漫反射
|
|
24
|
-
|
|
25
|
-
vec3 light = (ambient + diffuse) * u_light_intensity; // 叠加光照并乘以强度
|
|
26
|
-
|
|
27
|
-
FragColor = vec4(v_color.rgb * light, v_color.a);
|
|
28
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
use serde::{Deserialize, Serialize};
|
|
2
|
-
|
|
3
|
-
use crate::{scene::Scene, utils::{Interaction, MeshData, VisualShape, VisualStyle}, Shape};
|
|
4
|
-
|
|
5
|
-
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
|
6
|
-
pub struct Sphere {
|
|
7
|
-
pub center: [f32; 3],
|
|
8
|
-
pub radius: f32,
|
|
9
|
-
pub quality: u32,
|
|
10
|
-
|
|
11
|
-
pub style: VisualStyle,
|
|
12
|
-
pub interaction: Interaction,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
impl Into<Shape> for Sphere {
|
|
16
|
-
fn into(self) -> Shape {
|
|
17
|
-
Shape::Sphere(self)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
impl Sphere {
|
|
22
|
-
pub fn new(center: [f32; 3], radius: f32) -> Self {
|
|
23
|
-
Self {
|
|
24
|
-
center,
|
|
25
|
-
radius,
|
|
26
|
-
quality: 2,
|
|
27
|
-
style: VisualStyle {
|
|
28
|
-
opacity: 1.0,
|
|
29
|
-
visible: true,
|
|
30
|
-
..Default::default()
|
|
31
|
-
},
|
|
32
|
-
interaction: Default::default(),
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
pub fn center(mut self, center: [f32; 3])-> Self {
|
|
37
|
-
self.center = center;
|
|
38
|
-
self
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
pub fn set_radius(mut self, radius: f32)-> Self {
|
|
42
|
-
self.radius = radius;
|
|
43
|
-
self
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
pub fn clickable(mut self, val: bool) -> Self {
|
|
47
|
-
self.interaction.clickable = val;
|
|
48
|
-
self
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
pub fn to_mesh(&self, scale: f32) -> MeshData {
|
|
52
|
-
let mut vertices = Vec::new();
|
|
53
|
-
let mut normals = Vec::new();
|
|
54
|
-
let mut indices = Vec::new();
|
|
55
|
-
let mut colors = Vec::new();
|
|
56
|
-
|
|
57
|
-
let lat_segments = 10 * self.quality;
|
|
58
|
-
let lon_segments = 20 * self.quality;
|
|
59
|
-
|
|
60
|
-
let r = self.radius;
|
|
61
|
-
let [cx, cy, cz] = self.center;
|
|
62
|
-
|
|
63
|
-
// 基础颜色(带透明度)
|
|
64
|
-
let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
|
|
65
|
-
let alpha = self.style.opacity.clamp(0.0, 1.0);
|
|
66
|
-
let color_rgba = [base_color[0], base_color[1], base_color[2], alpha];
|
|
67
|
-
|
|
68
|
-
for i in 0..=lat_segments {
|
|
69
|
-
let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
|
|
70
|
-
let sin_theta = theta.sin();
|
|
71
|
-
let cos_theta = theta.cos();
|
|
72
|
-
|
|
73
|
-
for j in 0..=lon_segments {
|
|
74
|
-
let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
|
|
75
|
-
let sin_phi = phi.sin();
|
|
76
|
-
let cos_phi = phi.cos();
|
|
77
|
-
|
|
78
|
-
let nx = sin_theta * cos_phi;
|
|
79
|
-
let ny = cos_theta;
|
|
80
|
-
let nz = sin_theta * sin_phi;
|
|
81
|
-
|
|
82
|
-
let x = cx + r * nx;
|
|
83
|
-
let y = cy + r * ny;
|
|
84
|
-
let z = cz + r * nz;
|
|
85
|
-
|
|
86
|
-
vertices.push([x, y, z].map(|x| x * scale));
|
|
87
|
-
normals.push([nx, ny, nz].map(|x| x * scale));
|
|
88
|
-
colors.push(color_rgba); // 每个顶点同样颜色
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
for i in 0..lat_segments {
|
|
93
|
-
for j in 0..lon_segments {
|
|
94
|
-
let first = i * (lon_segments + 1) + j;
|
|
95
|
-
let second = first + lon_segments + 1;
|
|
96
|
-
|
|
97
|
-
indices.push(first);
|
|
98
|
-
indices.push(second);
|
|
99
|
-
indices.push(first + 1);
|
|
100
|
-
|
|
101
|
-
indices.push(second);
|
|
102
|
-
indices.push(second + 1);
|
|
103
|
-
indices.push(first + 1);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
MeshData {
|
|
108
|
-
vertices,
|
|
109
|
-
normals,
|
|
110
|
-
indices,
|
|
111
|
-
colors: Some(colors),
|
|
112
|
-
transform: None,
|
|
113
|
-
is_wireframe: self.style.wireframe,
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
impl VisualShape for Sphere {
|
|
119
|
-
fn style_mut(&mut self) -> &mut VisualStyle {
|
|
120
|
-
&mut self.style
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
pub trait UpdateSphere {
|
|
125
|
-
fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
impl UpdateSphere for Scene {
|
|
129
|
-
fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere)) {
|
|
130
|
-
if let Some(Shape::Sphere(sphere)) = self.named_shapes.get_mut(id) {
|
|
131
|
-
f(sphere);
|
|
132
|
-
} else {
|
|
133
|
-
panic!("Sphere with ID '{}' not found or is not a Sphere", id);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/bg_fragment.glsl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|