cosmol-viewer 0.1.1.dev1__tar.gz → 0.1.1.dev2__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.

Files changed (27) hide show
  1. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/Cargo.lock +5 -5
  2. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/Cargo.toml +2 -1
  3. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/PKG-INFO +1 -1
  4. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/lib.rs +2 -4
  5. cosmol_viewer-0.1.1.dev2/crates/core/src/parser/mod.rs +2 -0
  6. cosmol_viewer-0.1.1.dev2/crates/core/src/parser/sdf.rs +197 -0
  7. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/scene.rs +8 -2
  8. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/canvas.rs +49 -46
  9. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/fragment.glsl +2 -4
  10. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/vertex.glsl +1 -1
  11. cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/mod.rs +3 -0
  12. cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/molecules.rs +276 -0
  13. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shapes/sphere.rs +7 -10
  14. cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/stick.rs +153 -0
  15. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/utils.rs +28 -16
  16. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/python/Cargo.toml +1 -1
  17. cosmol_viewer-0.1.1.dev2/crates/python/build.rs +22 -0
  18. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/python/src/lib.rs +24 -61
  19. cosmol_viewer-0.1.1.dev2/crates/python/src/parser.rs +24 -0
  20. cosmol_viewer-0.1.1.dev2/crates/python/src/shapes.rs +97 -0
  21. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/pyproject.toml +1 -2
  22. cosmol_viewer-0.1.1.dev1/crates/core/src/shapes/mod.rs +0 -1
  23. cosmol_viewer-0.1.1.dev1/crates/python/build.rs +0 -42
  24. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/Cargo.toml +0 -0
  25. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/bg_fragment.glsl +0 -0
  26. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/bg_vertex.glsl +0 -0
  27. {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/mod.rs +0 -0
@@ -699,7 +699,7 @@ dependencies = [
699
699
 
700
700
  [[package]]
701
701
  name = "cosmol_viewer"
702
- version = "0.1.1-nightly.1"
702
+ version = "0.1.1-nightly.2"
703
703
  dependencies = [
704
704
  "bytemuck",
705
705
  "cosmol_viewer_core",
@@ -717,7 +717,7 @@ dependencies = [
717
717
 
718
718
  [[package]]
719
719
  name = "cosmol_viewer_core"
720
- version = "0.1.1-nightly.1"
720
+ version = "0.1.1-nightly.2"
721
721
  dependencies = [
722
722
  "bytemuck",
723
723
  "eframe",
@@ -731,7 +731,7 @@ dependencies = [
731
731
 
732
732
  [[package]]
733
733
  name = "cosmol_viewer_gui"
734
- version = "0.1.1-nightly.1"
734
+ version = "0.1.1-nightly.2"
735
735
  dependencies = [
736
736
  "bytemuck",
737
737
  "cosmol_viewer_core",
@@ -765,7 +765,7 @@ dependencies = [
765
765
 
766
766
  [[package]]
767
767
  name = "cosmol_viewer_wasm"
768
- version = "0.1.1-nightly.1"
768
+ version = "0.1.1-nightly.2"
769
769
  dependencies = [
770
770
  "cosmol_viewer_core",
771
771
  "eframe",
@@ -3160,7 +3160,7 @@ dependencies = [
3160
3160
 
3161
3161
  [[package]]
3162
3162
  name = "test"
3163
- version = "0.1.1-nightly.1"
3163
+ version = "0.1.1-nightly.2"
3164
3164
  dependencies = [
3165
3165
  "cosmol_viewer",
3166
3166
  "cosmol_viewer_core",
@@ -1,6 +1,6 @@
1
1
  [workspace.package]
2
2
  edition = "2024"
3
- version = "0.1.1-nightly.1"
3
+ version = "0.1.1-nightly.2"
4
4
  authors = ["9028 wjt@cosmol.org"]
5
5
  repository = "https://github.com/COSMol-repl/COSMol-viewer"
6
6
  homepage = "https://github.com/COSMol-repl/COSMol-viewer"
@@ -19,3 +19,4 @@ glam = { version = "0.30.3" , features = ["serde"] }
19
19
  serde_json = "1.0.140"
20
20
  sha2 = "0.10.9"
21
21
  hex = "0.4.3"
22
+ ipc-channel = "0.20.0"
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cosmol-viewer
3
- Version: 0.1.1.dev1
3
+ Version: 0.1.1.dev2
4
4
  Summary: Molecular visualization tools
5
5
  Author-email: 95028 <wjt@cosmol.org>
@@ -1,20 +1,18 @@
1
1
  mod shader;
2
2
  use std::{
3
- collections::HashMap,
4
3
  sync::{Arc, Mutex},
5
4
  };
6
5
 
7
6
  pub mod utils;
7
+ pub mod parser;
8
8
 
9
9
  use eframe::egui::{self, Color32, Stroke};
10
10
 
11
-
12
- use serde::{Deserialize, Serialize};
13
11
  use shader::Canvas;
14
12
 
15
13
  pub use crate::utils::{Shape};
16
14
  pub mod shapes;
17
- use crate::{scene::Scene, shader::CameraState, utils::ToMesh};
15
+ use crate::{scene::Scene};
18
16
 
19
17
  pub mod scene;
20
18
 
@@ -0,0 +1,2 @@
1
+ pub mod sdf;
2
+
@@ -0,0 +1,197 @@
1
+ #[derive(Debug, Clone)]
2
+ pub struct Atom {
3
+ pub atom: String,
4
+ pub elem: String,
5
+ pub x: f32,
6
+ pub y: f32,
7
+ pub z: f32,
8
+ pub serial: usize,
9
+ pub index: usize,
10
+ pub hetflag: bool,
11
+ pub bonds: Vec<usize>,
12
+ pub bond_order: Vec<f32>,
13
+ pub properties: std::collections::HashMap<String, String>,
14
+ }
15
+
16
+ pub type Molecule = Vec<Atom>;
17
+ pub type MoleculeData = Vec<Molecule>;
18
+
19
+ #[derive(Default)]
20
+ pub struct ParserOptions {
21
+ pub keep_h: bool,
22
+ pub multimodel: bool,
23
+ pub onemol: bool,
24
+ }
25
+
26
+ pub fn parse_sdf(sdf: &str, options: &ParserOptions) -> MoleculeData {
27
+ let lines: Vec<&str> = sdf.lines().collect();
28
+ if lines.len() > 3 && lines[3].len() > 38 {
29
+ let version = lines[3][34..39].trim();
30
+ match version {
31
+ "V3000" => parse_v3000(lines, options),
32
+ _ => parse_v2000(lines, options),
33
+ }
34
+ } else {
35
+ vec![vec![]]
36
+ }
37
+ }
38
+
39
+ fn parse_v2000(mut lines: Vec<&str>, options: &ParserOptions) -> MoleculeData {
40
+ let mut molecules = vec![vec![]];
41
+ let mut current = 0;
42
+
43
+ while lines.len() >= 4 {
44
+ let header = lines[3];
45
+ let atom_count = header[0..3].trim().parse::<usize>().unwrap_or(0);
46
+ let bond_count = header[3..6].trim().parse::<usize>().unwrap_or(0);
47
+
48
+ if atom_count == 0 || lines.len() < 4 + atom_count + bond_count {
49
+ break;
50
+ }
51
+
52
+ let mut serial_to_index = vec![None; atom_count];
53
+ let mut offset = 4;
54
+ let start = molecules[current].len();
55
+
56
+ for i in 0..atom_count {
57
+ let line = lines[offset + i];
58
+ let elem = line[31..34].trim();
59
+ let elem_cap = capitalize(elem);
60
+ if elem_cap != "H" || options.keep_h {
61
+ let atom = Atom {
62
+ atom: elem_cap.clone(),
63
+ elem: elem_cap,
64
+ x: line[0..10].trim().parse().unwrap_or(0.0),
65
+ y: line[10..20].trim().parse().unwrap_or(0.0),
66
+ z: line[20..30].trim().parse().unwrap_or(0.0),
67
+ serial: start + i,
68
+ index: molecules[current].len(),
69
+ hetflag: true,
70
+ bonds: vec![],
71
+ bond_order: vec![],
72
+ properties: std::collections::HashMap::new(),
73
+ };
74
+ serial_to_index[i] = Some(molecules[current].len());
75
+ molecules[current].push(atom);
76
+ }
77
+ }
78
+
79
+ offset += atom_count;
80
+
81
+ for i in 0..bond_count {
82
+ let line = lines[offset + i];
83
+ let from = line[0..3].trim().parse::<usize>().unwrap_or(0).saturating_sub(1);
84
+ let to = line[3..6].trim().parse::<usize>().unwrap_or(0).saturating_sub(1);
85
+ let order = line[6..].trim().parse::<f32>().unwrap_or(1.0);
86
+ if let (Some(f), Some(t)) = (serial_to_index.get(from).and_then(|x| *x), serial_to_index.get(to).and_then(|x| *x)) {
87
+ molecules[current][f].bonds.push(t);
88
+ molecules[current][f].bond_order.push(order);
89
+ molecules[current][t].bonds.push(f);
90
+ molecules[current][t].bond_order.push(order);
91
+ }
92
+ }
93
+
94
+ let mut next_offset = offset + bond_count;
95
+ if options.multimodel {
96
+ if !options.onemol {
97
+ molecules.push(vec![]);
98
+ current += 1;
99
+ }
100
+ while next_offset < lines.len() && lines[next_offset] != "$$$$" {
101
+ next_offset += 1;
102
+ }
103
+ lines.drain(0..=next_offset);
104
+ } else {
105
+ break;
106
+ }
107
+ }
108
+
109
+ molecules
110
+ }
111
+
112
+ fn parse_v3000(mut lines: Vec<&str>, options: &ParserOptions) -> MoleculeData {
113
+ let mut molecules = vec![vec![]];
114
+ let mut current = 0;
115
+
116
+ while lines.len() >= 8 {
117
+ if !lines[4].starts_with("M V30 BEGIN CTAB") || !lines[5].starts_with("M V30 COUNTS") {
118
+ break;
119
+ }
120
+
121
+ let counts: Vec<_> = lines[5][13..].split_whitespace().collect();
122
+ let atom_count = counts.get(0).and_then(|s| s.parse::<usize>().ok()).unwrap_or(0);
123
+ let bond_count = counts.get(1).and_then(|s| s.parse::<usize>().ok()).unwrap_or(0);
124
+ let mut offset = 7;
125
+
126
+ let mut serial_to_index = vec![None; atom_count];
127
+ let start = molecules[current].len();
128
+
129
+ for i in 0..atom_count {
130
+ let line = lines[offset + i];
131
+ let parts: Vec<_> = line[6..].split_whitespace().collect();
132
+ if parts.len() > 4 {
133
+ let elem_cap = capitalize(parts[1]);
134
+ if elem_cap != "H" || options.keep_h {
135
+ let atom = Atom {
136
+ atom: elem_cap.clone(),
137
+ elem: elem_cap,
138
+ x: parts[2].parse().unwrap_or(0.0),
139
+ y: parts[3].parse().unwrap_or(0.0),
140
+ z: parts[4].parse().unwrap_or(0.0),
141
+ serial: start + i,
142
+ index: molecules[current].len(),
143
+ hetflag: true,
144
+ bonds: vec![],
145
+ bond_order: vec![],
146
+ properties: std::collections::HashMap::new(),
147
+ };
148
+ serial_to_index[i] = Some(molecules[current].len());
149
+ molecules[current].push(atom);
150
+ }
151
+ }
152
+ }
153
+
154
+ offset += atom_count + 1; // skip "END ATOM"
155
+ offset += 1; // BEGIN BOND
156
+
157
+ for i in 0..bond_count {
158
+ let line = lines[offset + i];
159
+ let parts: Vec<_> = line[6..].split_whitespace().collect();
160
+ if parts.len() > 3 {
161
+ let from = parts[2].parse::<usize>().unwrap_or(0).saturating_sub(1);
162
+ let to = parts[3].parse::<usize>().unwrap_or(0).saturating_sub(1);
163
+ let order = parts[1].parse::<f32>().unwrap_or(1.0);
164
+ if let (Some(f), Some(t)) = (serial_to_index.get(from).and_then(|x| *x), serial_to_index.get(to).and_then(|x| *x)) {
165
+ molecules[current][f].bonds.push(t);
166
+ molecules[current][f].bond_order.push(order);
167
+ molecules[current][t].bonds.push(f);
168
+ molecules[current][t].bond_order.push(order);
169
+ }
170
+ }
171
+ }
172
+
173
+ let mut next_offset = offset + bond_count;
174
+ if options.multimodel {
175
+ if !options.onemol {
176
+ molecules.push(vec![]);
177
+ current += 1;
178
+ }
179
+ while next_offset < lines.len() && lines[next_offset] != "$$$$" {
180
+ next_offset += 1;
181
+ }
182
+ lines.drain(0..=next_offset);
183
+ } else {
184
+ break;
185
+ }
186
+ }
187
+
188
+ molecules
189
+ }
190
+
191
+ fn capitalize(s: &str) -> String {
192
+ let mut chars = s.chars();
193
+ match chars.next() {
194
+ Some(first) => first.to_ascii_uppercase().to_string() + &chars.as_str().to_ascii_lowercase(),
195
+ None => String::new(),
196
+ }
197
+ }
@@ -11,6 +11,7 @@ pub struct Scene {
11
11
  pub camera_state: CameraState,
12
12
  pub named_shapes: HashMap<String, Shape>,
13
13
  pub unnamed_shapes: Vec<Shape>,
14
+ pub scale: f32,
14
15
  }
15
16
 
16
17
  impl Scene {
@@ -18,7 +19,7 @@ impl Scene {
18
19
  self.named_shapes
19
20
  .values()
20
21
  .chain(self.unnamed_shapes.iter())
21
- .map(|s| s.to_mesh())
22
+ .map(|s| s.to_mesh(self.scale))
22
23
  .collect()
23
24
  }
24
25
 
@@ -28,9 +29,14 @@ impl Scene {
28
29
  camera_state: CameraState::new(1.0),
29
30
  named_shapes: HashMap::new(),
30
31
  unnamed_shapes: Vec::new(),
32
+ scale: 1.0,
31
33
  }
32
34
  }
33
35
 
36
+ pub fn scale(&mut self, scale: f32) {
37
+ self.scale = scale;
38
+ }
39
+
34
40
  pub fn add_shape<S: Into<Shape>>(&mut self, shape: S, id: Option<&str>) {
35
41
  let shape = shape.into();
36
42
  if let Some(id) = id {
@@ -46,7 +52,7 @@ impl Scene {
46
52
  }
47
53
  }
48
54
 
49
- pub fn update_shape<S: Into<Shape>>(&mut self, shape: S, id: &str) {
55
+ pub fn update_shape<S: Into<Shape>>(&mut self, id: &str, shape: S) {
50
56
  let shape = shape.into();
51
57
  if let Some(existing_shape) = self.named_shapes.get_mut(id) {
52
58
  *existing_shape = shape;
@@ -322,12 +322,6 @@ impl Shader {
322
322
  fn paint(&mut self, gl: &glow::Context, aspect_ratio: f32, camera_state: CameraState) {
323
323
  use glow::HasContext as _;
324
324
 
325
- let proj = if aspect_ratio > 1.0 {
326
- Mat4::from_scale([1.0 / aspect_ratio, 1.0, 1.0].into())
327
- } else {
328
- Mat4::from_scale([1.0, aspect_ratio, 1.0].into())
329
- };
330
-
331
325
  let camera_position = -camera_state.direction * camera_state.distance;
332
326
  let camera_direction = camera_state.direction;
333
327
  let camera_up = camera_state.up;
@@ -346,19 +340,19 @@ impl Shader {
346
340
  };
347
341
 
348
342
  unsafe {
343
+ // 背面剔除 + 深度测试
349
344
  gl.enable(glow::CULL_FACE);
350
345
  gl.cull_face(glow::BACK);
351
- gl.front_face(glow::CCW); // 如果你的三角形是逆时针定义的
346
+ gl.front_face(glow::CCW);
352
347
 
353
- gl.enable(glow::DEPTH_TEST); // 开启深度测试
354
- gl.depth_func(glow::LEQUAL); // 设置深度测试规则
348
+ gl.enable(glow::DEPTH_TEST);
349
+ gl.depth_func(glow::LEQUAL);
355
350
 
356
- gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT); // 清除颜色和深度
351
+ gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT);
357
352
 
358
- // 然后绘制背景(禁用深度),再绘制模型
359
- gl.disable(glow::DEPTH_TEST);
353
+ // === 绘制背景 ===
354
+ gl.disable(glow::DEPTH_TEST); // ✅ 背景不需要深度
360
355
  gl.use_program(Some(self.program_bg));
361
-
362
356
  gl.uniform_3_f32_slice(
363
357
  gl.get_uniform_location(self.program_bg, "background_color")
364
358
  .as_ref(),
@@ -366,19 +360,29 @@ impl Shader {
366
360
  );
367
361
  gl.draw_arrays(glow::TRIANGLES, 0, 6);
368
362
 
369
- // 再开启深度测试绘制场景
363
+ // === 绘制场景 ===
370
364
  gl.enable(glow::DEPTH_TEST);
365
+ // gl.depth_mask(false); // ✅ 关键:恢复写入深度缓冲区
366
+
367
+ // gl.enable(glow::BLEND);
368
+ // gl.blend_func_separate(
369
+ // glow::ONE,
370
+ // glow::ONE, // 颜色:累加所有透明颜色
371
+ // glow::ZERO,
372
+ // glow::ONE_MINUS_SRC_ALPHA, // alpha:按透明度混合
373
+ // );
374
+
371
375
  gl.use_program(Some(self.program));
372
376
 
373
377
  gl.uniform_matrix_4_f32_slice(
374
378
  gl.get_uniform_location(self.program, "u_mvp").as_ref(),
375
379
  false,
376
- (proj * camera.view_proj()).as_ref(),
380
+ (camera.view_proj(aspect_ratio)).as_ref(),
377
381
  );
378
382
  gl.uniform_matrix_4_f32_slice(
379
383
  gl.get_uniform_location(self.program, "u_model").as_ref(),
380
384
  false,
381
- (camera.u_model()).as_ref(),
385
+ (camera.view_matrix()).as_ref(),
382
386
  );
383
387
  gl.uniform_matrix_3_f32_slice(
384
388
  gl.get_uniform_location(self.program, "u_normal_matrix")
@@ -396,7 +400,7 @@ impl Shader {
396
400
  );
397
401
 
398
402
  // 应用模型变换
399
- let transformed_pos = camera.u_model() * light_pos_homogeneous;
403
+ let transformed_pos = camera.view_matrix() * light_pos_homogeneous;
400
404
 
401
405
  // 提取前三个分量 (xyz)
402
406
  let transformed_pos_xyz = [transformed_pos.x, transformed_pos.y, transformed_pos.z];
@@ -416,7 +420,7 @@ impl Shader {
416
420
  );
417
421
 
418
422
  // 应用模型变换
419
- let transformed_camera_pos = camera.u_model() * camera_pos_homogeneous;
423
+ let transformed_camera_pos = camera.view_matrix() * camera_pos_homogeneous;
420
424
 
421
425
  // 提取前三个分量 (xyz)
422
426
  let transformed_camera_pos_xyz = [
@@ -446,7 +450,7 @@ impl Shader {
446
450
  self.dirty = false;
447
451
  }
448
452
 
449
- // 初始化时或每帧渲染前
453
+ // 绑定并上传缓冲
450
454
  gl.bind_vertex_array(Some(self.vertex_array));
451
455
  gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
452
456
  gl.buffer_data_u8_slice(
@@ -456,7 +460,6 @@ impl Shader {
456
460
  );
457
461
 
458
462
  gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
459
-
460
463
  gl.buffer_data_u8_slice(
461
464
  glow::ELEMENT_ARRAY_BUFFER,
462
465
  bytemuck::cast_slice(&self.indices),
@@ -536,6 +539,7 @@ pub struct Camera {
536
539
  }
537
540
 
538
541
  impl Camera {
542
+ /// 假定模型空间 == 世界空间
539
543
  pub fn new(position: [f32; 3], forward: [f32; 3], up: [f32; 3], fov: f32, scale: f32) -> Self {
540
544
  let z = Vec3::from(forward).normalize();
541
545
  let up = Vec3::from(up);
@@ -548,45 +552,44 @@ impl Camera {
548
552
  x: x.into(),
549
553
  y: y.into(),
550
554
  fov,
551
- scale: scale,
555
+ scale,
552
556
  }
553
557
  }
554
558
 
555
- pub fn u_model(&self) -> Mat4 {
556
- let (x, y, z, pos) = (self.x, self.y, self.z, self.position);
557
-
558
- let shear = Mat4::from_cols_array_2d(&[
559
- [x[0], x[1], x[2], 0.0],
560
- [y[0], y[1], y[2], 0.0],
561
- [z[0], z[1], z[2], 0.0],
562
- [0.0, 0.0, 0.0, 1.0],
563
- ])
564
- .transpose();
565
-
566
- let translate = Mat4::from_translation(-Vec3::from(pos));
559
+ /// 从世界空间变换到相机空间
560
+ pub fn view_matrix(&self) -> Mat4 {
561
+ let pos = Vec3::from(self.position);
562
+ let center = pos + Vec3::from(self.z);
563
+ let up = Vec3::from(self.y);
567
564
 
568
- return shear * translate;
565
+ Mat4::look_at_rh(pos, center, up)
569
566
  }
570
567
 
571
- pub fn view_proj(&self) -> Mat4 {
572
- let proj = Mat4::from_cols_array_2d(&[
573
- [1.0, 0.0, 0.0, 0.0],
574
- [0.0, 1.0, 0.0, 0.0],
575
- [0.0, 0.0, 1.0, 0.0],
576
- [0.0, 0.0, self.scale, 1.0],
577
- ])
578
- .transpose();
568
+ /// 3D 场景投影成 2D 的视图
569
+ pub fn projection_matrix(&self, aspect: f32) -> Mat4 {
570
+ // 如果用 scale 控制的是放大倍率,可以解释为正交投影的比例因子
571
+ let s = self.scale;
572
+
573
+ // 你可以换成 perspective_rh(self.fov, aspect, near, far)
574
+ Mat4::orthographic_rh(
575
+ -s * aspect, s * aspect, // left, right
576
+ -s, s, // bottom, top
577
+ -1000.0, 1000.0 // near, far
578
+ )
579
+ }
579
580
 
580
- return proj * self.u_model();
581
+ /// 相机变换矩阵 = 投影 × 视图变换
582
+ pub fn view_proj(&self, aspect: f32) -> Mat4 {
583
+ self.projection_matrix(aspect) * self.view_matrix()
581
584
  }
582
585
 
586
+ /// 法线矩阵:模型矩阵的 3x3 的逆转置
583
587
  pub fn normal_matrix(&self) -> Mat3 {
584
- let model = self.u_model(); // Mat4
585
- let mat3 = Mat3::from_mat4(model); // Extract the upper-left 3x3 matrix
586
- mat3.inverse().transpose()
588
+ Mat3::from_mat4(self.view_matrix()).inverse().transpose()
587
589
  }
588
590
  }
589
591
 
592
+
590
593
  pub struct Light {
591
594
  pub position: [f32; 3],
592
595
  pub color: [f32; 3],
@@ -16,15 +16,13 @@ void main() {
16
16
  vec3 view_dir = normalize(u_view_pos - v_frag_pos); // 计算从片元到相机的方向
17
17
  vec3 halfway_dir = normalize(light_dir + view_dir); // halfway 向量用于 Blinn-Phong 高光
18
18
 
19
- float diff = max(dot(normal, light_dir), 0.0); // 漫反射项(Lambert)
19
+ float diff = max(dot(normal, light_dir), 0.0); // 漫反射项(Lambert)
20
20
  float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0); // 高光项,32 是 shininess 参数
21
21
 
22
22
  vec3 ambient = 0.3 * u_light_color; // 环境光,固定系数0.2
23
23
  vec3 diffuse = diff * u_light_color; // 漫反射
24
- // vec3 specular = spec * u_light_color; // 高光
25
24
 
26
- vec3 light = (ambient + diffuse) * u_light_intensity; // 叠加光照并乘以强度
27
- // vec3 light = (ambient + diffuse + specular) * u_light_intensity; // 叠加光照并乘以强度
25
+ vec3 light = (ambient + diffuse) * u_light_intensity; // 叠加光照并乘以强度
28
26
 
29
27
  FragColor = vec4(v_color.rgb * light, v_color.a);
30
28
  }
@@ -15,7 +15,7 @@ out vec4 v_color;
15
15
  void main() {
16
16
  vec4 world_pos = u_model * vec4(a_position, 1.0);
17
17
  v_frag_pos = world_pos.xyz;
18
- v_normal = -normalize(u_normal_matrix * a_normal);
18
+ v_normal = normalize(u_normal_matrix * a_normal);
19
19
  v_color = a_color;
20
20
  gl_Position = u_mvp * vec4(a_position, 1.0);
21
21
  }
@@ -0,0 +1,3 @@
1
+ pub mod sphere;
2
+ pub mod stick;
3
+ pub mod molecules;