3D and Materials: Perspective runs 0° to 160°, as documented

The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.

- The eye stands where a sphere about the object's centre, √3 times its
  half size (the box around it fits inside), fills that angle of the
  view: 0° is none, wider brings the eye nearer, and at 160° it is just
  outside the sphere, never inside the object. The documentation doesn't
  say how the angle places the eye; docs/three-d.md says this is our
  reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
  an earlier file or script is read as the angle that shows exactly the
  same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
  angle given too wins. effect.setParams turns a stored strength into the
  angle before applying new values. The parameter description names
  `perspective` as the earlier form, so effect.apply and effect.setParams
  still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
  60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.

# Conflicts:
#	docs/target-app-parity.md
This commit is contained in:
Ayberk A. authored and bflatastic committed 2026-10-14 05:10:09 +07:00
1 parent a3357121fb
commit 39db6abfce
16 files changed
+231 -49

No files matched your search

+31 -6
View File
@@ -311,8 +311,8 @@ fn set_params(s: &mut Session, p: &Value) -> Result<Value> {
&& visible != Some(false)
{
let mut prospective = effects::merged_params(&e.id, &e.params);
if let (Some(cur), Some(new)) = (prospective.as_object_mut(), params.as_object()) {
cur.extend(new.clone());
if let (Some(cur), Value::Object(new)) = (prospective.as_object_mut(), effects::upgrade_params(&e.id, &params)) {
cur.extend(new);
}
if e.id == effects::REVOLVE {
effects::validate_revolve(n, &prospective).map_err(|e| bad(C, e))?;
@@ -323,14 +323,15 @@ fn set_params(s: &mut Session, p: &Value) -> Result<Value> {
}
let ids = edit_effects(s, p, item, C, "Effect Options", &format!("{C}: no effect at index {index}"), |fx| {
let Some(e) = fx.get_mut(index) else { return false };
if let (Value::Object(new), cur) = (&params, &mut e.params) {
if let (Value::Object(new), cur) = (effects::upgrade_params(&e.id, &params), &mut e.params) {
if !cur.is_object() {
*cur = effects::merged_params(&e.id, &Value::Null);
}
// Earlier parameters (a Perspective strength) take their current form first, so the
// new values replace them and the rest is kept.
*cur = effects::upgrade_params(&e.id, cur);
if let Value::Object(m) = cur {
for (k, v) in new {
m.insert(k.clone(), v.clone());
}
m.extend(new);
}
}
if let Some(v) = visible {
@@ -602,6 +603,30 @@ mod tests {
assert!(err.to_string().contains("requires a path"), "{err}");
}
/// Scripts from before the Perspective angle give `perspective` as a 0–100 % strength: it is
/// stored as the angle that shows the same view, and a later angle replaces an earlier strength.
#[test]
fn an_earlier_perspective_strength_is_set_as_its_angle() {
let (mut s, id) = session_with_rect();
s.execute("effect.apply", &json!({"effect": effects::EXTRUDE, "ids": [id.0], "item": null, "params": {"perspective": 60}})).unwrap();
let params = |s: &Session| node(s, id).appearance.effects[0].params.clone();
let angle_of = |pct: f64| effects::merged_params(effects::EXTRUDE, &json!({"perspective": pct}))["perspectiveAngle"].as_f64().unwrap();
let angle = angle_of(60.0);
assert!((angle - 30.12).abs() < 0.01, "{angle}");
assert!(
params(&s).get("perspective").is_none() && (params(&s)["perspectiveAngle"].as_f64().unwrap() - angle).abs() < 1e-12,
"{}",
params(&s)
);
s.execute("effect.setParams", &json!({"index": 0, "params": {"perspective": 100}, "ids": [id.0], "item": null})).unwrap();
assert!((params(&s)["perspectiveAngle"].as_f64().unwrap() - angle_of(100.0)).abs() < 1e-12);
// A file of then: the strength stored; setting an angle replaces it.
std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).node_mut(id).unwrap().appearance.effects[0].params =
json!({"depth": 50, "perspective": 60});
s.execute("effect.setParams", &json!({"index": 0, "params": {"perspectiveAngle": 120}, "ids": [id.0], "item": null})).unwrap();
assert_eq!(params(&s), json!({"depth": 50, "perspectiveAngle": 120}));
}
fn effects_for_test(s: &Session, id: NodeId) -> vectorcraft_doc::Node {
effects::revolve_art(&node(s, id)).unwrap()
}