Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] Previously defined coordinates shouldn't be affected by group()-scoped axis transformations #601

Open
tapyu opened this issue May 21, 2024 · 13 comments
Assignees

Comments

@tapyu
Copy link
Contributor

tapyu commented May 21, 2024

Consider the following MWE:

#import "@preview/cetz:0.2.2": canvas, plot, draw, coordinate, vector
#set page(width: auto, height: auto, margin: .5cm)

#canvas({
  draw.rect((0,0), (1,1), name: "rect")
  let test = (rel: (0deg, 2), to: "rect.north-east")
  draw.group({
    // draw.rotate(45deg, origin: test)
    draw.circle(test, radius: (0.7, 0.2), anchor: "center")
    draw.get-ctx(ctx => {
      let (ctx, a) = coordinate.resolve(ctx, test)
      draw.content(test, [#a], frame: "rect", stroke: none, fill: white)
    })
  })
})

This produces

image

The command draw.rotate(45deg, origin: test) shouldn't affect test since the rotation is scoped within group() and rotate() is being applied after test is defined. Therefore, uncommenting rotate() should lead to a rotation in circle() around its center.

However, by uncommenting it, we somehow obtain

image

This new (and wrong) position is the because rotate() is changing the x-y axis used to determine test, which shouldn't happen.

@tapyu
Copy link
Contributor Author

tapyu commented May 21, 2024

From the manual

Elements after the group are not affected by the changes inside the group.

Well, maybe I misunderstood, but group() should also prevent changes from being applied in elements before the group, no? Otherwise, how could I use test within group() if I want to rotate it?

@tapyu tapyu closed this as completed May 21, 2024
@tapyu
Copy link
Contributor Author

tapyu commented May 21, 2024

In fact, I can retrieve test by using resolve() and them using the resolved coordinates

draw.get-ctx(ctx => {
      let (ctx, a) = coordinate.resolve(ctx, test)
      draw.content(test, [#a], frame: "rect", stroke: none, fill: white)
      draw.rotate(45deg, origin: a)
      draw.circle(a, radius: (0.7, 0.2), anchor: "center")
    })

However, I still don't know if this is the expected behavior. Please, let me know if this is indeed a bug or not

@tapyu tapyu reopened this May 21, 2024
@johannes-wolf
Copy link
Member

I don't really get what you mean, it looks correct to me. What are you expecting?

@tapyu
Copy link
Contributor Author

tapyu commented May 21, 2024

I was expecting that

#import "@preview/cetz:0.2.2": canvas, plot, draw, coordinate, vector
#set page(width: auto, height: auto, margin: .5cm)

#canvas({
  draw.rect((0,0), (1,1), name: "rect")
  let test = (rel: (0deg, 2), to: "rect.north-east")
  draw.group({
    draw.rotate(45deg, origin: test)
    draw.circle(test, radius: (0.7, 0.2), anchor: "center")
    draw.get-ctx(ctx => {
      let (ctx, a) = coordinate.resolve(ctx, test)
      draw.content(test, [#a], frame: "rect", stroke: none, fill: white)
    })
  })
})

produces

image

but it produced

image

The whole point is that rotate() is affecting the value of test, and I wasn't expecting that since test is defined outside group().

@johannes-wolf
Copy link
Member

johannes-wolf commented May 21, 2024

It is def. broken.

@johannes-wolf
Copy link
Member

Oh, I am confused.
I think it is correct.

@johannes-wolf
Copy link
Member

johannes-wolf commented May 21, 2024

The problem here is, that test gets resolve with the rotation active.
You've got two solutions:

group({
  anchor("tmp", test) // Force resolve coordinate first
  rotate(45deg)
  circle(...)
})

Or use set-origin first:

group({
  set-origin(test)
  rotate(45deg)
  circle((0,0), ...) // At (0,0)
})

Tikz has \begin{scope}[rotate around], so it can handle this case. Maybe we want something similar for our transformations?

@johannes-wolf johannes-wolf self-assigned this May 21, 2024
@tapyu
Copy link
Contributor Author

tapyu commented May 22, 2024

The problem here is, that test gets resolve with the rotation active.

This behavior is counterintuitive. test is set before any rotation is performed. How/why does test gets resolved with the rotation active?

Tikz has \begin{scope}[rotate around], so it can handle this case. Maybe we want something similar for our transformations?

If we agree that it is sensible that test should not be affected by rotate(), we don't need to do anything as draw.rotate(45deg, origin: test) would work as expected. I believe the is the least confusing and simplest behavior one wants to achieve. After all, if we want that test to be affect by the rotation, we can simply define it after rotating the axis:

draw.rotate(45deg, origin: (rel: (0deg, 2), to: "rect.north-east")) // rotation
let test = (rel: (0deg, 2), to: "rect.north-east") // defining `test` on the rotated axis

@tapyu tapyu changed the title [bug] group() is not scoping transformations properly when relative coordinates are used [bug] Previously defined coordinates shouldn't be affected by group()-scoped axis transformations May 22, 2024
@johannes-wolf
Copy link
Member

johannes-wolf commented May 22, 2024

It is set before any rotation, but test is just an array which represents a cetz coordinate. Because of how Typst/cetz work, it is not possible to resolve it without calling to cetz. If you create an anchor, that anchor gets resolved where it is declared.

It would work if you call let (_, test) = coordinate.resolve(...).

For cetz to be able to resolve and return coordinates, Typst would need to allow to set some sort of writable global context, which it does not.

@tapyu
Copy link
Contributor Author

tapyu commented May 22, 2024

:(

@johannes-wolf
Copy link
Member

I think the best thing to do is allowing anchors at the root scope (without a group). That would allow defining coordinates with the current transformation taken into account.

@tapyu
Copy link
Contributor Author

tapyu commented May 22, 2024

I am not sure how your idea would work. I just tried to help Cetz saying that it is reasonable test isn't changed by transformations made after its definition. If it is not possible due to Typst's internal behavior, I don't know how would be the solution.

However, I assure that creating anchors like anchor("tmp", test) to get arrays resolved where it declared is more a workaround than a good solution. Beginners will have a hard time to figure it out.

@tapyu
Copy link
Contributor Author

tapyu commented May 24, 2024

Hi there! A potentially interesting aspect about this issue:

#import "@preview/cetz:0.2.2": canvas, draw, coordinate
#import draw: *
#set page(width: auto, height: auto, margin: .5cm)

#canvas({
  rect((0,0), (1,1), name: "rect")
  let test = "rect.north-east"
  group({
    rotate(45deg)
    circle(test, radius: (0.7, 0.2), anchor: "center")
    get-ctx(ctx => {
      let (ctx, a) = coordinate.resolve(ctx, test)
      content(test, [#a], frame: "rect", stroke: none, fill: white)
    })
  })
})

image

To my surprise, removing the relative shift in test leads to the expected position. So AFAIU the issue isn't that the array or the anchor itself. Rather, it is due to the fact that the context used inside (where we rotate) and outside (where we don't want that the rotation happens) group() is the same.

Please, let me try to propose a solution for this: isn't possible to work with two contexts? One to apply any transformation inside and the other for anything outside group(). In this way, test (or anything outside group()) would be resolved by CeTZ with the nonrotated context where it was defined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants