So I have this Panel class. It's a little like a Window where you can resize, close, add buttons, sliders, etc. Much like the status screen in Morrowind if any of you remember. The behavior I want is that when a sprite is outside of the panel's bounds it doesn't get drawn and if it's partially outside only the part inside gets drawn.
So what it does right now is first get a rectangle that represents the bounds of the panel, and a rectangle for the sprite, it finds the rectangle of intersection between the two then translates that intersection to the local coordinates of the sprite rectangle and uses that for the source rectangle. It works and as clever as I feel the code is I can't shake the feeling that there's a better way to do this. Also, with this set up I cannot utilize a global transformation matrix for my 2D camera, everything in the "world" must be passed a camera argument to draw. Anyway, here's the code I have:
for the Intersection:
public static Rectangle? Intersection(Rectangle rectangle1, Rectangle rectangle2)
{
if (rectangle1.Intersects(rectangle2))
{
if (rectangle1.Contains(rectangle2))
{
return rectangle2;
}
else if (rectangle2.Contains(rectangle1))
{
return rectangle1;
}
else
{
int x = Math.Max(rectangle1.Left, rectangle2.Left);
int y = Math.Max(rectangle1.Top, rectangle2.Top);
int height = Math.Min(rectangle1.Bottom, rectangle2.Bottom) - Math.Max(rectangle1.Top, rectangle2.Top);
int width = Math.Min(rectangle1.Right, rectangle2.Right) - Math.Max(rectangle1.Left, rectangle2.Left);
return new Rectangle(x, y, width, height);
}
}
else
{
return null;
}
}
and for actually drawing on the panel:
public void DrawOnPanel(IDraw sprite, SpriteBatch spriteBatch)
{
Rectangle panelRectangle = new Rectangle(
(int)_position.X,
(int)_position.Y,
_width,
_height);
Rectangle drawRectangle = new Rectangle();
drawRectangle.X = (int)sprite.Position.X;
drawRectangle.Y = (int)sprite.Position.Y;
drawRectangle.Width = sprite.Width;
drawRectangle.Height = sprite.Height;
if (panelRectangle.Contains(drawRectangle))
{
sprite.Draw(
spriteBatch,
drawRectangle,
null);
}
else if (Intersection(panelRectangle, drawRectangle) == null)
{
return;
}
else if (Intersection(panelRectangle, drawRectangle).HasValue)
{
Rectangle intersection = Intersection(panelRectangle, drawRectangle).Value;
if (Intersection(panelRectangle, drawRectangle) == drawRectangle)
{
sprite.Draw(spriteBatch, intersection, intersection);
}
else
{
sprite.Draw(
spriteBatch,
intersection,
new Rectangle(
intersection.X - drawRectangle.X,
intersection.Y - drawRectangle.Y,
intersection.Width,
intersection.Height));
}
}
}
So I guess my question is, is there a better way to do this?
Update: Just found out about the ScissorRectangle property. This seems like a decent way to do this; it requires a RasterizerState object to be made and passed into the spritebatch.Begin overload that accepts it. Seems like this might be the best bet though. There's also the Viewport which I can apparently change around. Thoughts? :)
There are several ways to limit drawing to a portion of the screen. If the area is rectangular (which seems to be the case here), you could set the viewport (see GraphicsDevice) to the panel's surface.
For non-rectangular areas, you can use the stencil buffer or use some tricks with the depth buffer. Draw the shape of the surface in the stencil buffer or the depth buffer, set your render state to draw only pixels located in the shape you just rendered in the stencil/depth buffer, finally render your sprites.
One way of doing this is simple per-pixel collision. Although this is a bad idea if the sprites are large or numerous, this can be a very easy and fast way to get the job done with small sprites. First, do a bounding circle or bounding square collision check against the panel to see if you even need to do per-pixel detection.
Then, create a contains method that checks if the position, scale, and rotation of the sprite put it so far inside the panel that it must be totally enclosed by the panel, so you don't need per-pixel collision in that case. This can be done pretty easily by just creating a bounding square that has the width and height of the length of the sprite's diagonal, and checking for collision with that.
Finally, if both of these fail, we must do per-pixel collision. Go through and check against every pixel in the sprite to see if it is within the bounds of the panel. If it isn't set the alpha value of the pixel to 0.
Thats it.
Related
I'm writing my first Qt 5 application... This uses a third-party map library (QGeoView).
I need to draw an object (something like a stylized airplane) over this map. Following the library coding guidelines, I derived from the base class QGVDrawItem my QGVAirplane.
The airplane class contains heading and position values: such values must be used to draw the airplane on the map (of course in the correct position and with correct heading). The library requires QGVDrawItem derivatives to override three base class methods:
QPainterPath projShape() const;
void projPaint(QPainter* painter);
void onProjection(QGVMap* geoMap)
The first method is used to achieve the area of the map that needs to be updated. The second is the method responsible to draw the object on the map. The third method is needed to reproject the point from the coordinate space on the map (it's not relevant for the solution of my problem).
My code looks like this:
void onProjection(QGVMap* geoMap)
{
QGVDrawItem::onProjection(geoMap);
mProjPoint = geoMap->getProjection()->geoToProj(mPoint);
}
QPainterPath projShape() const
{
QRectF _bounding = createGlyph().boundingRect();
double _size = fmax(_bounding.height(), _bounding.width());
QPainterPath _bounding_path;
_bounding_path.addRect(0,0,_size,_size);
_bounding_path.translate(mProjPoint.x(), mProjPoint.y());
return _bounding_path;
}
// This function creates the path containing the airplane glyph
// along with its label
QPainterPath createGlyph() const
{
QPainterPath _path;
QPolygon _glyph = QPolygon();
_glyph << QPoint(0,6) << QPoint(0,8) << QPoint(14,6) << QPoint(28,8) << QPoint(28,6) << QPoint(14,0);
_path.addPolygon(_glyph);
_path.setFillRule(Qt::FillRule::OddEvenFill);
_path.addText(OFF_X_TEXT, OFF_Y_TEXT, mFont , QString::number(mId));
QTransform _transform;
_transform.rotate(mHeading);
return _transform.map(_path);
}
// This function is the actual painting method
void drawGlyph(QPainter* painter)
{
painter->setRenderHints(QPainter::Antialiasing, true);
painter->setBrush(QBrush(mColor));
painter->setPen(QPen(QBrush(Qt::black), 1));
QPainterPath _path = createGlyph();
painter->translate(mProjPoint.x(), mProjPoint.y());
painter->drawPath(_path);
}
Of course:
mProjPoint is the position of the airplane,
mHeading is the heading (the direction where the airplane is pointing),
mId is a number identifying the airplane (will be displayed as a label under airplane glyph),
mColor is the color assigned to the airplane.
The problem here is the mix of rotation and translation. Transformation: since the object is rotated, projShape() methods return a bounding rectangle that's not fully overlapping the object drawn on the map...
I also suspect that the center of the object is not correctly pointed on mProjPoint. I tried many times trying to translate the bounding rectangle to center the object without luck.
Another minor issue is the fillup of the font... the label under the airplane glyph is not solid, but it is filled with the same color of the airplane.
How can I fix this?
Generically speaking, the general pattern for rotation is to scale about the origin first and then finish with your final translation.
The following is pseudocode, but it illustrates the need to shift your object's origin to (0, 0) prior to doing any rotation or scaling. After the rotate and scale are done, the object can be moved back from (0, 0) back to where it came from. From here, any post-translation step may be applied.
translate( -origin.x, -origin.y );
rotate( angle );
scale( scale.x, scale y);
translate( origin.x, origin.y );
translate( translation.x, translation.y )
I finally managed to achieve the result I meant....
QPainterPath projShape() const
{
QPainterPath _path;
QRectF _glyph_bounds = _path.boundingRect();
QPainterPath _textpath;
_textpath.addText(0, 0, mFont, QString::number(mId));
QRectF _text_bounds = _textpath.boundingRect();
_textpath.translate(_glyph_bounds.width()/2-_text_bounds.width()/2, _glyph_bounds.height()+_text_bounds.height());
_path.addPath(_textpath);
QTransform _transform;
_transform.translate(mProjPoint.x(),mProjPoint.y());
_transform.rotate(360-mHeading);
_transform.translate(-_path.boundingRect().width()/2, -_path.boundingRect().height()/2);
return _transform.map(_path);
}
void projPaint(QPainter* painter)
{
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
painter->setRenderHint(QPainter::HighQualityAntialiasing, true);
painter->setBrush(QBrush(mColor));
painter->setPen(QPen(QBrush(Qt::black), 1));
painter->setFont(mFont);
QPainterPath _path = projShape();
painter->drawPath(_path);
}
Unluckly I still suffer the minor issue with text fill mode:
I would like to have a solid black fill for the text instead of the mColor fill I use for the glyph/polygon.
Above is an example of my problem. I have two alpha masks that are exactly the same, just a circle white gradient with transparent background.
I am drawing to a RenderTexture2D that is rendered above the screen to creating lighting. It clears a semi transparent black color, and then the alpha masks are drawn in the correct position to appear like lights..
On their own it works fine, but if two clash, like the below "torch" against the blue glowing mushrooms, you can see the bounding box transparency is overwriting the already drawn orange glow.
Here is my approach:
This is creating the render target:
RenderTarget2D = new RenderTarget2D(Global.GraphicsDevice, Global.Resolution.X+4, Global.Resolution.Y+4);
SpriteBatch = new SpriteBatch(Global.GraphicsDevice);
This is drawing to the render target:
private void UpdateRenderTarget()
{
Global.GraphicsDevice.SetRenderTarget(RenderTarget2D);
Global.GraphicsDevice.Clear(ClearColor);
// Draw textures
float i = 0;
foreach (DrawableTexture item in DrawableTextures)
{
i += 0.1f;
item.Update?.Invoke(item);
SpriteBatch.Begin(SpriteSortMode.Immediate, item.Blend,
SamplerState.PointClamp, DepthStencilState.Default,
RasterizerState.CullNone);
SpriteBatch.Draw(
item.Texture,
(item.Position - Position) + (item.Texture.Size() / 2 * (1 - item.Scale)),
null,
item.Color,
0,
Vector2.Zero,
item.Scale,
SpriteEffects.None,
i
);
SpriteBatch.End();
}
Global.GraphicsDevice.SetRenderTarget(null);
}
I have heard about depth stencils etc.. and I feel like I have tried so many combinations of things but I am still getting the issue. I haven't had any troubles with this while building all the other graphics in my game.
Any help is greatly appreciated thanks! :)
Ah, this turned out to be a problem with the BlendState itself rather than the SpriteBatch. I had created a custom BlendState "Multiply" which I picked up online that was causing the issue.
"whats causing" the problem was the real question here.
This was the solution to get my effect without "overlapping":
public static BlendState Lighting = new BlendState
{
ColorSourceBlend = Blend.One,
ColorDestinationBlend = Blend.One,
AlphaSourceBlend = Blend.Zero,
AlphaDestinationBlend = Blend.InverseSourceColor
};
This allows the textures to overlap, and also "subtracts" from the "darkness" layer. It would be easier to see if the darkness was more opaque.
I have answered this just incase some other fool mistakes a blend state problem with the sprite batch itself.
Openlayers provides useful functions for drawing boxes and rectangles and also has ol.geom.Geometry.prototype.rotate(angle, anchor) for rotating a geometry around a certain anchor. Is it possible to lock the rotation of a box/rectangle while modifying it?
Using the OpenLayers example located here to draw a box with a certain rotation to illustrate the point:
I would like the box/rectangle to maintain its rotation while still being able to drag the sides longer and shorter. Is there a simple way to achieve this?
Answering with the solution I came up with.
First of all, add the feature(s) to a ModifyInteraction so you are able to modify by dragging the corners of the feature.
this.modifyInteraction = new Modify({
deleteCondition: eventsCondition.never,
features: this.drawInteraction.features,
insertVertexCondition: eventsCondition.never,
});
this.map.addInteraction(this.modifyInteraction);
Also, add event handlers upon the events "modifystart" and "modifyend".
this.modifyInteraction.on("modifystart", this.modifyStartFunction);
this.modifyInteraction.on("modifyend", this.modifyEndFunction);
The functions for "modifystart" and "modifyend" look like this.
private modifyStartFunction(event) {
const features = event.features;
const feature = features.getArray()[0];
this.featureAtModifyStart = feature.clone();
this.draggedCornerAtModifyStart = "";
feature.on("change", this.changeFeatureFunction);
}
private modifyEndFunction(event) {
const features = event.features;
const feature = features.getArray()[0];
feature.un("change", this.changeFeatureFunction);
// removing and adding feature to force reindexing
// of feature's snappable edges in OpenLayers
this.drawInteraction.features.clear();
this.drawInteraction.features.push(feature);
this.dispatchRettighetModifyEvent(feature);
}
The changeFeatureFunction is below. This function is called for every single change which is done to the geometry as long as the user is still modifying/dragging one of the corners. Inside this function, I made another function to adjust the modified rectangle into a rectangle again. This "Rectanglify"-function moves the corners which are adjacent to the corner which was just moved by the user.
private changeFeatureFunction(event) {
let feature = event.target;
let geometry = feature.getGeometry();
// Removing change event temporarily to avoid infinite recursion
feature.un("change", this.changeFeatureFunction);
this.rectanglifyModifiedGeometry(geometry);
// Reenabling change event
feature.on("change", this.changeFeatureFunction);
}
Without going into too much detail, the rectanglify-function needs to
find rotation of geometry in radians
inversely rotate with radians * -1 (e.g. geometry.rotate(radians * (-1), anchor) )
update neighboring corners of the dragged corner (easier to do when we have a rectangle which is parallel to the x and y axes)
rotate back with the rotation we found in 1
--
In order to get the rotation of the rectangle, we can do this:
export function getRadiansFromRectangle(feature: Feature): number {
const coords = getCoordinates(feature);
const point1 = coords[0];
const point2 = coords[1];
const deltaY = (point2[1] as number) - (point1[1] as number);
const deltaX = (point2[0] as number) - (point1[0] as number);
return Math.atan2(deltaY, deltaX);
}
I’m doing video processing with GPUImage2. When the app starts, I create a hexagonal grid and add it to my cameraView. The grid is fullscreen and consists of about 100 of hexagons.
In general, what I’m trying to achieve is
For each frame I want to find an average color (in RGB or even better HSV) within each cell of the grid.
When the color is determined, I want to draw something in the center of each hexagon depending on its average color.
I have an array with hexagons, each of them knows its vertexes’ coordinates and center.
I also have an array with UIBezierPaths which contains bounds of these hexagons (just in case).
So my code looks like this
class ViewController: UIViewController {
var hexagons = [HKHexagon]()
var hexagonsBounds = [UIBezierPath]()
let averageColorExtractor = AverageColorExtractor()
override func viewDidLoad() {
super.viewDidLoad()
do {
camera = try Camera(sessionPreset:AVCaptureSessionPreset1920x1080)
camera.delegate = self
cameraView.orientation = .landscapeLeft
camera --> cameraView
camera.startCapture()
drawGrid()
} catch {
fatalError("Could not initialize rendering pipeline: \(error)")
}
}
}
extension ViewController: CameraDelegate {
func didCaptureBuffer(_ sampleBuffer: CMSampleBuffer) {
for hexagon in hexagons {
}
}
}
I guess didCaptureBuffer() should be the place to apply averageColorExtractor to each hexagon but don’t have an idea what to do next..
I am new to iOS development and it’s the first time I use GPUImage2… Please, guide me in the right direction.
Not coding for your platform at all but GPU architecture allows to do it like this:
pass the image as texture
render the center points only as points
in fragment shader compute the avg color of hex around actual position
This is the hardest and most performance demanding part. If you compute just inscribed circle it is easy but for hexagon you need to compute which texel is inside and which not. For axis aligned hexagons you can divide hex into regions (2x rectangle, 4x triangle) for rotated hexes you need add transformation matrix.
compute/render output inside the center point.
I do not know what your framework can do for you from this. If you rendered stuff is bigger then just the center point then you need either use another pass in your render or use bigger primitive then points in #2 but that means you will compute the avg color for each rendered pixel which can slow things down a lot.
Take a look at GLSL shader that uses this technique (for entirely different task but the technique is the same):
How to implement 2D raycasting light effect in GLSL
If this is not adaptable to your platform then ignore this answer ...
I'm currently working on a XNA game prototype. I'm trying to achieve a isometric view of the game world (or is it othographic?? I'm not sure which is the right term for this projection - see pictures).
The world should a tile-based world made of cubic tiles (e.g. similar to Minecraft's world), and I'm trying to render it in 2D by using sprites.
So I have a sprite sheet with the top face of the cube, the front face and the side (visible side) face. I draw the tiles using 3 separate calls to drawSprite, one for the top, one for the side, one for the front, using a source rectangle to pick the face I want to draw and a destination rectangle to set the position on the screen according to a formula to convert from 3D world coordinates to isometric (orthographic?).
(sample sprite:
)
This works good as long as I draw the faces, but if I try to draw fine edges of each block (as per a tile grid) I can see that I get a random rendering pattern in which some lines are overwritten by the face itself and some are not.
Please note that for my world representation, X is left to right, Y is inside screen to outside screen, and Z is up to down.
In this example I'm working only with top face-edges. Here is what I get (picture):
I don't understand why some of the lines are shown and some are not.
The rendering code I use is (note in this example I'm only drawing the topmost layers in each dimension):
/// <summary>
/// Draws the world
/// </summary>
/// <param name="spriteBatch"></param>
public void draw(SpriteBatch spriteBatch)
{
Texture2D tex = null;
// DRAW TILES
for (int z = numBlocks - 1; z >= 0; z--)
{
for (int y = 0; y < numBlocks; y++)
{
for (int x = numBlocks - 1; x >=0 ; x--)
{
myTextures.TryGetValue(myBlockManager.getBlockAt(x, y, z), out tex);
if (tex != null)
{
// TOP FACE
if (z == 0)
{
drawTop(spriteBatch, x, y, z, tex);
drawTop(spriteBatch, x, y, z, outlineTexture);
}
// FRONT FACE
if(y == numBlocks -1)
drawFront(spriteBatch, x, y, z, tex);
// SIDE FACE
if(x == 0)
drawSide(spriteBatch, x, y, z, tex);
}
}
}
}
}
private void drawTop(SpriteBatch spriteBatch, int x, int y, int z, Texture2D tex)
{
int pX = OffsetX + (int)(x * TEXTURE_TOP_X_OFFRIGHT + y * TEXTURE_SIDE_X);
int pY = OffsetY + (int)(y * TEXTURE_TOP_Y + z * TEXTURE_FRONT_Y);
topDestRect.X = pX;
topDestRect.Y = pY;
spriteBatch.Draw(tex, topDestRect, TEXTURE_TOP_RECT, Color.White);
}
I tried using a different approach, creating a second 3-tiers nested for loop after the first one, so I keep the top face drawing in the first loop and the edge highlight in the second loop (I know, this is inefficient, I should also probably avoid having a method call for each tile to draw it, but I'm just trying to get it working for now).
The results are somehow better but still not working as expected, top rows are missing, see picture:
Any idea of why I'm having this problem? In the first approach it might be a sort of z-fighting, but I'm drawing sprites in a precise order so shouldn't they overwrite what's already there?
Thanks everyone
Whoa, sorry guys I'm an idiot :) I started the batch with SpriteBatch.begin(SpriteSortMode.BackToFront) but I didn't use any z-value in the draw.
I should have used SpriteSortMode.Deferred! It's now working fine. Thanks everyone!
Try tweaking the sizes of your source and destination rectangles by 1 or 2 pixels. I have a sneaking suspicion this has something to do with the way these rectangles are handled as sort of 'outlines' of the area to be rendered and a sort of off-by-one problem. This is not expert advice, just a fellow coder's intuition.
Looks like a sub pixel precision or scaling issue. Also try to ensure your texture/tile width/height is a power of 2 (32, 64, 128, etc.) as that could make the effect less bad as well. It's really hard to tell just from those pictures.
I don't know how/if you scale everything, but you should try to avoid rounding wherever possible (especially inside your drawTop() method). Every time you round some position/coordinate chances are good you might increase the error/random offsets. Try to use double (or better: float) coordinates instead of integer.