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.
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/Cargo.lock +5 -5
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/Cargo.toml +2 -1
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/PKG-INFO +1 -1
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/lib.rs +2 -4
- cosmol_viewer-0.1.1.dev2/crates/core/src/parser/mod.rs +2 -0
- cosmol_viewer-0.1.1.dev2/crates/core/src/parser/sdf.rs +197 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/scene.rs +8 -2
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/canvas.rs +49 -46
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/fragment.glsl +2 -4
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/vertex.glsl +1 -1
- cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/mod.rs +3 -0
- cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/molecules.rs +276 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shapes/sphere.rs +7 -10
- cosmol_viewer-0.1.1.dev2/crates/core/src/shapes/stick.rs +153 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/utils.rs +28 -16
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/python/Cargo.toml +1 -1
- cosmol_viewer-0.1.1.dev2/crates/python/build.rs +22 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/python/src/lib.rs +24 -61
- cosmol_viewer-0.1.1.dev2/crates/python/src/parser.rs +24 -0
- cosmol_viewer-0.1.1.dev2/crates/python/src/shapes.rs +97 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/pyproject.toml +1 -2
- cosmol_viewer-0.1.1.dev1/crates/core/src/shapes/mod.rs +0 -1
- cosmol_viewer-0.1.1.dev1/crates/python/build.rs +0 -42
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/Cargo.toml +0 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/bg_fragment.glsl +0 -0
- {cosmol_viewer-0.1.1.dev1 → cosmol_viewer-0.1.1.dev2}/crates/core/src/shader/bg_vertex.glsl +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,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
|
|
15
|
+
use crate::{scene::Scene};
|
|
18
16
|
|
|
19
17
|
pub mod scene;
|
|
20
18
|
|
|
@@ -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,
|
|
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
|
-
(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
555
|
+
scale,
|
|
552
556
|
}
|
|
553
557
|
}
|
|
554
558
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
let
|
|
559
|
-
|
|
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
|
-
|
|
565
|
+
Mat4::look_at_rh(pos, center, up)
|
|
569
566
|
}
|
|
570
567
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
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 =
|
|
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
|
}
|