Creating a dynamic sphere in OSG - c++

I want to create a dynamic sphere in OSG which would be created(moved) by the mouse left click at that location (center) and with a dynamic radius of the mouse pointer current location's distance to the center....
I understand that for this reason I need to create an osgGA::GUIEventHandler Object and implement the virtual handle function but I miss other details like finding the sphere object it self from the scene and changing its attributes.
I also tried to change A picking example but failed to detect the sphere in the nodePath or creating a new sphere

I would probably start by creating a custom class derived from one of the osgGA Manipulator classes.
You'll want to override the handle() method with something like this:
bool CustomManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
using namespace osgGA;
if (ea.getEventType()==GUIEventAdapter::FRAME)
{
if (_thrown) aa.requestRedraw();
}
else if (ea.getEventType()==GUIEventAdapter::PUSH)
{
// check if your sphere is picked using LineSegmentIntersector
// (like in the picking example) and set a flag
}
else if (ea.getEventType()==GUIEventAdapter::DRAG)
{
// update the position and radius of your sphere if the flag was set
}
else if (ea.getEventType()==GUIEventAdapter::RELEASE)
{
// release the sphere, unset the flag
}
return false;
}
Then remember to use setCameraManipulator() on your viewer to add this instead of the default TrackballManipulator.

Related

SFML: How to check if a point is contained in a group of transformed drawables

Context
I have a class representing a text box. the text box contains a header, some text and a rectangle to enclose the box. It only displays itself (for now):
struct Textbox : public sf::Drawable, public sf::Transformable{
sf::Text header;
sf::Text text;
sf::RectangleShape border;
Textbox(){
// set relative locations of the members
header.setPosition(0,0);
auto header_bounds = header.getGlobalBounds();
// the text should be just below the header
text.setPosition(0, header_bounds.top + header_bounds.height);
auto source_bounds = text.getGlobalBounds();
// this function just returns a rectangle enclosing two rectangles
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
// this function sets the position, width and length of border to be equal to rect's.
setRectParams(border, rect);
}
void draw(sf::RenderTarget& target, sf::RenderStates states){
states.transform = getTransform();
target.draw(header,states);
target.draw(text,states);
target.draw(border,states);
};
The Problem
What I want
I want to add a contains method. It should return true if coor is inside the border of the box. Here is my naive implementation:
bool Textbox::contains(sf::Vector2i coor) const {
return border.getGlobalBounds().contains(coor.x, coor.y);
}
Why this implementation doesn't work
This implementation breaks when I move the Textbox via the Transformable non-virtual functions. The Textbox moves and it also draws the shapes as transformed. But! It does not actually transform them! it only displays them as transformed. So the border doesn't even know it has been moved.
Possible solutions
I can add all the functions of the Transformable API to this class, thus shadowing them and calling transform by myself on each of the members. I don;t like this because it make me write sooo much more code than I wanted. It also raises the question of how to tackle the double transforms (the one for the Textbox and the others for it's members).
I can write a completely different class Group that holds a vector of drawables and transformables and it has all that shadowing API mechanism. All that is left is to inherit from it. This doesn't actually sound that bad.
I heard about Entity System Component - it's just sound pretty overkill.
I can apply the transform when contains is called. The function is const - it's a query. Also, it's bad design to update your data on seemingly random calls.
just as before just that the transform applies to a function-local rectangle. This smells too - why do I call the transform functions on the whole Textbox just so it would apply them on every method call (so far just it's draw and contains but down the line who knows)
Make the members mutable and somehow transform them inside the draw method. This smell hackish.
The question
How do I group transformations onto multiple entities via an ergonomic API?
The only method that you really need to 'change', but to be fair add on your own is getGlobalBounds().
When you are inheriting from sf::Transformable, sf::Drawable you should treat the base class (your Textbox struct) as a shape itself therfore you just need to call myTextbox.getGlobalBounds().contains(x,y), where myTextbox is a Textbox.
Using your own code:
struct Textbox : public sf::Drawable, public sf::Transformable{
sf::Text header;
sf::Text text;
sf::RectangleShape border;
sf::FloatRect getGlobalBounds() const {
auto header_bounds = header.getGlobalBounds();
auto source_bounds = text.getGlobalBounds();
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
//Don't really know what it does but let say that it returns Top and Left as 0, and calculates Height, Width.
return sf::FloatRect(getPosition(), sf::Vector2f(rect.width,rect.height));
}
};
But you still have to manage the rotation, resizing,etc. when calculating globalBounds.
EDIT:
One way to implement rotation and scaling.
sf::FloatRect getGlobalBounds() const {
auto header_bounds = header.getGlobalBounds();
auto source_bounds = text.getGlobalBounds();
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
//Don't really know what it does but let say that it returns Top and Left as 0, and calculates Height, Width.
sf::RectangleShape textbox(sf::Vector2f(rect.width, rect.height));
//at this point textbox = globalBounds of Textbox without transformations
textbox.setOrigin(getOrigin());//setOrigin (point of transformation) before transforming
textbox.setScale(getScale());
textbox.setRotation(getRotation());
textbox.setPosition(getPosition());
//after transformation get the bounds
return textbox.getGlobalBounds();
}
The solution might be much more simple than you expect. Instead of applying all the transforms to the transformable children/members, just de-transform the point you want to check (take it to local space).
Try this:
bool Textbox::contains(sf::Vector2i coor) const {
// Get point in the local space of the rectangle
sf::Transform inverseTr = this->getInverseTransform();
sf::Vector2f pointAsLocal = inverseTr.transformPoint(coor.x, coor.y);
// Check if the point, now in local space, is containted in the rectangle
return border.getLocalBounds().contains(pointAsLocal);
// ^
// Important! Use local bounds here, not global
}
Why does this work?
Math!
When you work with transformation matrices, you can think of them as portals between spaces. You have a local space where no transformation have been applied, and you have a final space, where all transformations are applied.
The problem with global bounds of a transformable member is that they belong neither to the local space nor the final space. They are just a rectangle bounding the shape in a possibly intermediate space where this bounds doesn't even take rotation into account.
What we are doing here is taking the coordinates, that exist in the final space, and take them to the local space of the rectangle, thanks to the inverse transformation matrix. So no matter how many translations, rotations or scales (or even skews, if you have customized the matrix) you apply to the rectangle. The inverse matrix takes the point to a new space where you can just check if it belongs, as if no transformation have ever been applied.

How to have a class with an instance of its predecessor and continue its heirarchy for all new instances?

I'll try to be as specific as possible so it's easy to understand.
Let's say you have a class that holds a rectangle in the form of (x, y, w, h) when working on a 2d plane with (0,0) being the top left of the display. The first instance of this class holds the values (0,0,1920,1080), which sets the display dimensions for a program.
Now, from here, I want to have all the next instances of Rectangle to exist within the previous one, such that their coordinates act as relative positions to it.
For example, let's work with the next 2 Rectangle instances. I'll declare one called rect1 to have the values (100,100,500,500), and then the second instance called rect2 to exist within rect1. Its values will be (50,50,50,50).
Now, I want the heirarchy of values to apply to all instances to allow the relative positioning. What this would mean is that (relative to the original display's rectangle's first instance), rect1 will have a relative position in the form (x,y) of (100,100), however rect2 will have a relative position of (150,150) TO THE ORIGINAL DISPLAY'S RECTANGLE's FIRST INSTANCE.
But, to make life easy and follow the heirarchy, rect2 will have a RELATIVE POSITION TO rect1 of (50,50), yet in terms of the predasessor of rect1, its relative position is (150,150), as explained before.
Here is a picture of what I am trying to demonstrate:
So, with that type of structure in mind, how would I be able to translate that concept into code that follows the same idea?
Probably the easiest method is to store a reference to the "parent" Rectangle. So given you example it would look something like this:
When you are looking for the relative location you would simply need to check if the parent is null and return the positions added to the parents:
class Rectangle {
private:
int x, y, w, h;
Rectangle *parent;
public:
Point getLocation() {
if (this.parent != NULL) {
// return x and y
}
// return x + this.parent.x and y + this.parent.y
}
int getXOffset() {
if (this.parent != NULL) {
return this.x + this.parent->getXOffset();
}
return this.x;
}
// other methods...
}
Note that for this structure to work properly you will want to implement methods (getXOffset() for example) to walk the parent hierarchy to recursively sum the offset of every parent Rectangle rather than directlry accessing the registered parent and missing 'grandparents' or even 'grand grandparents'.

Transformations igrnores sf::Sprite's origin

Transforming a sprite in SFML, does not regard it's new origin.
In my case sf::Sprite is rotating around the axis that is in the left top corner ({0,0}) regardless its origin. Setting new origin with .setOrigin() earlier takes no effect.
I am sure that sprite is getting the right origin position earlier which is center of rectangle.
My code:
In each of my Card class constructors I set the origin of my sprite.
card_sprite.setOrigin(Card::get_default_single_card_size().x*Game::get_scale()/2,Card::get_default_single_card_size().y*Game::get_scale()/2);
And then in my Deck class which behaves like std::stack of Cards I use function:
void Deck::push(const Card& crd)
{
push_back(crd);
..//
std::default_random_engine generator;
std::uniform_real_distribution<float> distributor(0,360);
top().setRotation(distributor(generator));
}
Card::setRotaion() looks like this, (which stil rotates card around top left corner) :
void Card::setRotation(float angle)
{
card_sprite.setRotation(angle);
}
Thanks for help in advance
Edit: Actually most methods in sf::Transform accept extra arguments to specify a center for the transformation, as per https://stackoverflow.com/users/7703024/super 's comment on my question on the same theme : How to "set the origin" of a Transform in sfml
I'm not too sure from your code, but I might've come up against a similar problem.
I "solved" it (in a very not ideal way) by replacing every call to a sfml drawing function with a call to a custom function when using sf::Transforms.
eg: instead of doing something like:
window.draw(thing, my_transform);
I had to do :
draw_transformed(thing, my_transform, window)
Where the code of draw_transformed looks like this:
void draw_transformed (sf::Shape const& thing, sf::Transform const& t, sf::RenderWindow& window) // cf note (1)
{
sf::Vector2f pos = thing.getPosition();
sf::Transform go_to_zero;
go_to_zero.translate(-pos);
sf::Transform go_back;
go_back.translate(pos);
sf::Transform conjugated_transform = go_back * t * go_to_zero ;
window.draw(thing, conjugated_transform);
}
(1) we can't use sf::Drawable as the type of thing because in sfml not all drawable things have a getPosition method, so we have to overload the function or do something "complicated" to go beyond this example.

Adding layer of drawable objects to separate logic from graphics

I am trying to program classical snake game. I have logic working (eating, growing moving etc.). Now I want to add some nice graphics. So far I have some ugly graphics i made with this code:
(pseudocode)
void drawWorld(const std::vector<Tileable*> world_map) {
for each Tileable {
get position of Tileable
get color of Tileable
draw rectangle at position with color
}
}
Class Tileable represents objects on Snakes map -> apples, walls even snake itself. It stores objects position, what happens when snake collides with object etc -> logic.
BUT
Woldn it be nice if Apples were circles, not rectangles?
So I want to add with class Drawable with method .draw() and derived classes DrawableSnake, DrawableApple, DrawableWall, DrawableWhatever, each having its own draw() method.
The problem is converting Tielable subclasses from collection world_map to apropriate subclass of Drawable, to have something like this:
void drawWorld(const std::vector<Tileable*> world_map) {
for each Tileable {
get drawable from tileable
drawable.draw()
}
}
I am thinking about using something like factory with RTTI: (pseudocode)
Drawable convertToDrawable(Tileable* tileable){
if tileable is Apple return DrawableApple
if tileable is Wall return DrawableWall
etc.
}
Is there some nicer way to do this, without using RTTI?
This is a common problem in designing game engines. One way to solve this is to move to an entity-component based system.
The apples, walls and the snake would all be of the same class - 'Entity'. To each entity you would attach a list of components. In your case 'ComponentDrawable' could be a component and 'ComponentTileable' could be another. When you create the entity you initialize ComponentDrawable with the data it needs to draw that specific entity.
You need a way to get a component of a certain type from the entity. A simple system could have each entity having an array of component pointers. Each component type would have a unique index in that array. An empty slot in the array would have a NULL value.
template<typename T> T* getComponent( Entity *entity ) {
entity.component_array[T::index]
}
And your modified pseudocode example would then end up looking something like this:
void drawWorld(const std::vector<Entity*> world_map) {
for each entity in world_map {
ComponentDrawable *drawable = getComponent<ComponentDrawable>( entity )
if( drawable )
drawable->draw()
}
}
Some people like to keep functionality out of the components and only use them as carriers of data. If you would like to go that route then a data driven system could use the data from the ComponentDrawable to draw the correct representation of each entity.

Can't animate QTransform in qgraphicsItem

I'm trying do some 3D animation in GraphicsScene, for example, to rotate pictures in GraphicsScene (using class, subclassed from qPixmapItem and QObject, if it matters) with Animation framework.
Everything works fine, until i want to rotate pictures around vertical axis.
There is no way doing so via item.rotate(), so i'm using QTranform.
The problem is that doing so does not animate anything at all. What am i doing wrong?
P.S. I do not want use OpenGl for this.
Here is the way i'm doing it. This way works for animating simpler properties like pos, rotation(via rotation, setRotation)
My code :
// hybrid graphicsSceneItem, supporting animation
class ScenePixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QTransform transform READ transform WRITE setTransform)
public:
ScenePixmap(const QPixmap &pixmap, QObject* parent = NULL, QGraphicsItem* parentItem = NULL):
QObject(parent),
QGraphicsPixmapItem(pixmap, parentItem)
{}
};
Here is how I setup scene and animation:
//setup scene
//Unrelated stuff, loading pictures, etc.
scene = new QGraphicsScene(this);
foreach(const QPixmap& image, images)
{
ScenePixmap* item = new ScenePixmap(image);
item->moveBy(70*i, 0);
i++;
this->images.append(item);
scene->addItem(item);
}
}
ui->graphicsView->setBackgroundBrush(QBrush(Qt::black, Qt::SolidPattern));
ui->graphicsView->setScene(scene);
//setup animation
QTransform getTransform()
{
QTransform transform;
transform.rotate(-30, Qt::ZAxis);//also tried transform = transform.rotate(...)
return transform;
}
QAbstractAnimation* SetupRotationAnimation(ScenePixmap* pixmapItem)
{
QPropertyAnimation* animation = new QPropertyAnimation(pixmapItem, "transform");
animation->setDuration(1400);
animation->setStartValue( pixmapItem->transform());
animation->setEndValue(getTransform());//here i tried to multiply with default transform , this does not work either
return animation;
}
here is the way i start animation:
void MainWindow::keyPressEvent ( QKeyEvent * event )
{
if((event->modifiers() & Qt::ControlModifier))
{
QAnimationGroup* groupAnimation = new QParallelAnimationGroup();
foreach(ScenePixmap* image, images)
{
groupAnimation->addAnimation( SetupRotationAnimation(image));
}
groupAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}
}
EDIT[Solved] thx to Darko Maksimovic:
Here is the code that worked out for me:
QGraphicsRotation* getGraphicRotation()
{
QGraphicsRotation* transform = new QGraphicsRotation(this);
transform->setAxis(Qt::YAxis);
return transform;
}
QAbstractAnimation* SetupRotationAnimation(ScenePixmap* pixmapItem)
{
QGraphicsRotation* rotation = getGraphicRotation();
QPropertyAnimation* animation = new QPropertyAnimation(rotation, "angle");
animation->setDuration(1400);
animation->setStartValue( 0);
animation->setEndValue(45);
pixmapItem->setTransformOriginPoint(pixmapItem->boundingRect().center());
QList<QGraphicsTransform*> transfromations = pixmapItem->transformations();
transfromations.append(rotation);
pixmapItem->setTransformations(transfromations);
return animation;
}
I see you use QTransform. If you want only one rotation, and simple rotation that is, it is better that you use setRotation [don't forget about setTransformOriginPoint].
If you, however, want to remember many rotations, around different transform points for example, then you should use QGraphicsTransform, i.e. its specialised derived class, QGraphicsRotation, which you apply by calling setTransformations on a graphics object (you should first fetch the existing transformations by calling o.transformations(), append to this, then call setTransformations; if you keep the pointer to the added transformation you can also change it later directly).
From the code you posted I can't see where your error is coming from, but by using specialised functions you can avoid some of frequent problems.
P.S. I also see you didn't use prepareGeometryChange in the code you posted, so please be advised that this is necessary when transforming objects.
The problem is very simple. QVariantAnimation and QPropertyAnimation don't support QTransform. Heck, they don't even support unsigned integers at the moment. That's all there's to it. Per Qt documentation:
If you need to interpolate other variant types, including custom types, you have to implement interpolation for these yourself. To do this, you can register an interpolator function for a given type. This function takes 3 parameters: the start value, the end value and the current progress.
It'd might not be all that trivial to generate such an interpolator. Remember that you have to "blend" between two matrices, while maintaining the orthonormality of the matrix, and the visual effect. You'd need to decompose the matrix into separate rotation-along-axis, scaling, skew, etc. sub-matrices, and then interpolate each of them separately. No wonder the Qt folks didn't do it. It's probably much easier to do the inverse problem: generate the needed transformation matrix as a function of some parameter t, by composing the necessary rotations, translations, etc., all given as (perhaps constant) functions of t.
Just because a QVariant can carry a type doesn't mean it's supported by the default interpolator. There probably should be a runtime warning issued to this effect.