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

Allow easier translation/rotation of groups of primitives #194

Closed
SuperCuber opened this issue Sep 17, 2018 · 7 comments · Fixed by #484
Closed

Allow easier translation/rotation of groups of primitives #194

SuperCuber opened this issue Sep 17, 2018 · 7 comments · Fixed by #484

Comments

@SuperCuber
Copy link

Problem

There's no easy way to translate+rotate a group of primitives that represent a single object.

In ProcessingJS for example you would be able to translate and rotate the coordinate system itself, then draw the primitives, then restore the state to what it was before. Right now you need to calculate the position+rotation of each primitive yourself.

Proposed solutions:

  • Copy ProcessingJS's approach and allow manipulating the coordinate system (+ adding a builtin ability to push and pop the current state of the coordinate system to a stack)
  • Create some Canvas primitive that can be drawn to, and then drawn using a position+rotation onto the real screen

I am not completely familiar with this project so if I missed the intended way to do it, then I'd love to be pointed to docs of it. (Couldn't find any example of such a thing)

@mitchmindtree
Copy link
Member

Thanks for opening this!

Totally agreed there needs to be an easier solution for rotating multiple primitives at once!

While the processing approach of using a global rotation is very easy to add in and is easier to use with small sketches, it can be tricky to keep track of as programs grow and it can get easy to unexpectedly break things by adding or removing rotations or refactoring.

Maybe we can achieve something similar that feels as easy to use by adding in some explicit translation/rotation types (matrices under the hood) that can be chained together and used to position primitives as an alternative to the positioning and orientation methods.

@SuperCuber
Copy link
Author

@mitchmindtree I haven't personally encountered the problem of losing track since most usage of that feature goes along the lines of:

push_current_coordinate_system()
rotate(...)
scale(...)
translate(...)

draw_primitives()
draw_primitives()
draw_primitives() // Relative to 0,0

pop_coordinate_system()

Note that that code above doesn't need to start from an "identity matrix" state, it can be put in a function and used to draw sub-objects of objects for example. I think it would be useful to enforce/automate this pushing/popping using a constructor and destructor though.

@freesig
Copy link
Collaborator

freesig commented Oct 7, 2018

I agree that that global translates are easier with simple projects but they can get hard to track as the project grows. Especially with multiple threads.
It would be ideal if we could find a solution almost as easy to use but without the global state. We want nannou to be great for tiny sketches as well as large projects

@SuperCuber
Copy link
Author

@freesig Have you considered the Canvas solution? What do you think would be the pros/cons of that? To elaborate, Canvas can mimic the API of Draw and also have some sort of .canvas(...) function that allows drawing a canvas onto another canvas (Draw can be a Canvas too?).

So you would do something along the lines of

let draw = ...;
let my_object = /*some way to get a canvas? maybe Canvas::new or Canvas::from_wh()*/;
my_object.primitive().position(...);
my_object.primitive().position(...);
my_object.primitive().position(...);
draw.canvas(my_object).position(...).rotate(...).scale(...);

@mitchmindtree
Copy link
Member

@SuperCuber I really like the idea behind this and will certainly keep it in mind when I get around to addressing this :)

@mitchmindtree
Copy link
Member

mitchmindtree commented Jan 21, 2019

We've just been discussing this a little more on the slack.

Here's a little rough snippet of what I'm imagining:

let draw2 = draw.rotate(r).translate(xy).scale(s);
// You can now use all the same methods as draw but they will be translated/oriented/scaled via an inner transform matrix.
draw2.line().points(a, b);
draw2.rect();
// Then we can easily continue drawing with the original, untransformed draw.
draw.polygon().points(ps).color(red);
//etc

Transformations could be nested by calling the transform methods on the already transformed draw instance.

@SuperCuber
Copy link
Author

Possible use case to consider: having a function that draws an object.
With @mitchmindtree 's proposal it would probably look something like this.

impl CustomObject {
    fn draw(&self, transformed: /*the type*/) {
        // All drawing is relative to (0,0)
        transformed.primitive().xy(...);
        // Can also draw sub-custom-objects
        self.custom_sub_object.draw(transformed.translate(...));
    }
}

Another proposal that I haven't considered before (and might be already implemented in some form?) is to let structs implement a Drawable trait that is something along those lines:

trait Drawable {
    fn draw(&self, draw: Draw) {
        // draw relative to (0,0)
    }
}

And then you can do something like

let draw = ...;
let my_object = ...;
draw.drawable(my_object).xy(...);

Maybe there is a way to even combine those two approaches...

mitchmindtree added a commit to mitchmindtree/nannou that referenced this issue Mar 16, 2020
This overhauls the `Draw` API with some pretty major changes. The
largest of which, is that group transforms, blend modes and scissors are
now possible via the new `draw.transform(mat4)`,
`draw.blend(blend_desc)` and `draw.scissor(rect)` methods. Each of these
methods return a new `Draw` instance of the same type, but with the
given transform/blend/scissor applied! See [this
comment](nannou-org#194 (comment))
for a short demo of what this looks like. None of the examples have been
updated to take advantage of this yet, though there are many nature of
code and generative design examples that have been awaiting these
changes.

In order to enable these changes, the `Draw` backend has overgone a
major overhaul. Rather than rendering primitives when they are dropped,
we now instead create an inner list of `DrawCommand`s. These are
high-level commands that store minimal data and when combined,
completely describe the users drawing. The `draw::Renderer` now digests
a list of `DrawCommand`s, rather than taking the mesh directly. This
allows to provide the primitives access to more useful
state/caches/buffers during their rendering process. This also allows
the `draw::Renderer` to generate a much more advanced render pass by
creating unique `RenderPipeline`s per blend mode, unique `BindGroup`s
per unique texture view (to-be-implemented) and switching between
scissors during the render pass.

These changes have also allowed for providing access to a glyph cache to
the `Text` primitive. This means that, when `draw.text(str)` is used,
glyphs associated with characters of text will now be cached on the GPU
for efficient re-use. This means much faster drawing of large amounts of
text than was previously possible.

While this commit includes many additions, it also includes the removal
of a significant number of positioning, sizing and orientation methods.
Specifically, all methods that used to describe some relative
transformation (e.g. "down_from", "align_right", etc) have been removed
in favour of the new approach of using transformations. While these old
relative positioning methods were occasionally useful, the new group
transformations are strictly more flexible and provide a more familiar
API to users coming from processing, openframeworks, etc. Previously,
the **Draw** API used to construct an inner geometry graph in order to
keep track of relative transformations, however the behaviour of the
graph no longer and its ability to describe relative transforms between
any two primitives no longer made sense following the addition of group
transformations. For example, it's not clear what
`foo.align_left_of(bar)` would mean if both `foo` and `bar` were created
using different transformation matrices, as they may no longer reside on
the same plane to allow for any sort of alignment to occur. For the most
part, these relative positioning methods are more useful for user
interface, and still are available for widgets created with nannou's
`ui` API today.

Closes nannou-org#194.
mitchmindtree added a commit to mitchmindtree/nannou that referenced this issue Mar 16, 2020
This overhauls the `Draw` API with some pretty major changes. The
largest of which, is that group transforms, blend modes and scissors are
now possible via the new `draw.transform(mat4)`,
`draw.blend(blend_desc)` and `draw.scissor(rect)` methods. Each of these
methods return a new `Draw` instance of the same type, but with the
given transform/blend/scissor applied! See [this
comment](nannou-org#194 (comment))
for a short demo of what this looks like. None of the examples have been
updated to take advantage of this yet, though there are many nature of
code and generative design examples that have been awaiting these
changes.

In order to enable these changes, the `Draw` backend has overgone a
major overhaul. Rather than rendering primitives when they are dropped,
we now instead create an inner list of `DrawCommand`s. These are
high-level commands that store minimal data and when combined,
completely describe the users drawing. The `draw::Renderer` now digests
a list of `DrawCommand`s, rather than taking the mesh directly. This
allows to provide the primitives access to more useful
state/caches/buffers during their rendering process. This also allows
the `draw::Renderer` to generate a much more advanced render pass by
creating unique `RenderPipeline`s per blend mode, unique `BindGroup`s
per unique texture view (to-be-implemented) and switching between
scissors during the render pass.

These changes have also allowed for providing access to a glyph cache to
the `Text` primitive. This means that, when `draw.text(str)` is used,
glyphs associated with characters of text will now be cached on the GPU
for efficient re-use. This means much faster drawing of large amounts of
text than was previously possible.

While this commit includes many additions, it also includes the removal
of a significant number of positioning, sizing and orientation methods.
Specifically, all methods that used to describe some relative
transformation (e.g. "down_from", "align_right", etc) have been removed
in favour of the new approach of using transformations. While these old
relative positioning methods were occasionally useful, the new group
transformations are strictly more flexible and provide a more familiar
API to users coming from processing, openframeworks, etc. Previously,
the **Draw** API used to construct an inner geometry graph in order to
keep track of relative transformations, however the behaviour of the
graph no longer and its ability to describe relative transforms between
any two primitives no longer made sense following the addition of group
transformations. For example, it's not clear what
`foo.align_left_of(bar)` would mean if both `foo` and `bar` were created
using different transformation matrices, as they may no longer reside on
the same plane to allow for any sort of alignment to occur. For the most
part, these relative positioning methods are more useful for user
interface, and still are available for widgets created with nannou's
`ui` API today.

Closes nannou-org#194.
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

Successfully merging a pull request may close this issue.

3 participants