Problem with drawing a clock with Direct2D - c++

I am trying to draw a clock with Direct2D. The program correctly gets the current time, however the error is when Direct2D draws the clock, since it seems that the clock is out of date with the current time. To get the current time, use the GetLocalTime() function and then map the values to transform them to an angle, so that they can be drawn. How could I fix it?
...
renderTarget->BeginDraw();
renderTarget->Clear(ColorF(ColorF::Black));
GetLocalTime(&sysTime);
wstring text = L"Hour: " + to_wstring(sysTime.wHour)
+ L"\nMinute: " + to_wstring(sysTime.wMinute)
+ L"\nSecond: " + to_wstring(sysTime.wSecond);
brush->SetColor(ColorF(ColorF::White));
renderTarget->DrawTextW(text.c_str(), text.length(), textFormat, textRect, brush);
D2D1_POINT_2F centerPoint = Point2F(320, 240);
FLOAT hourAngle = map(sysTime.wHour%12, 0, 12, 0, 360);
FLOAT minuteAngle = map(sysTime.wMinute, 0, 60, 0, 360);
FLOAT secondAngle = map(sysTime.wSecond, 0, 60, 0, 360);
brush->SetColor(ColorF(ColorF::DeepPink));
renderTarget->DrawEllipse(D2D1::Ellipse(centerPoint, 150, 150), brush, 5);
renderTarget->SetTransform(Matrix3x2F::Rotation(secondAngle, centerPoint));
brush->SetColor(ColorF(ColorF::Blue));
renderTarget->DrawLine(centerPoint,Point2F(centerPoint.x,centerPoint.y + 150*0.9), brush, 10,lineStrokeStyle);
renderTarget->SetTransform(Matrix3x2F::Rotation(minuteAngle, centerPoint));
brush->SetColor(ColorF(ColorF::White));
renderTarget->DrawLine(centerPoint,Point2F(centerPoint.x,centerPoint.y + 150*0.7), brush, 10, lineStrokeStyle);
renderTarget->SetTransform(Matrix3x2F::Rotation(hourAngle, centerPoint));
brush->SetColor(ColorF(ColorF::GreenYellow));
renderTarget->DrawLine(centerPoint, Point2F(centerPoint.x,centerPoint.y + 150*0.5), brush, 10,lineStrokeStyle);
renderTarget->SetTransform(Matrix3x2F::Identity());
HRESULT hrErr = renderTarget->EndDraw();
if (hrErr != S_OK) {
MessageBox(hWnd, L"Direct2D Error", L"Direct2D Error", MB_OK | MB_ICONERROR);
SafeRelease(&brush);
SafeRelease(&renderTarget);
SafeRelease(&factory);
}
...

Yes, you can use GetLocalTime to get the current local date and time. The problem may be in mapping the time to an angle.
But these can be solved, because there are very detailed examples in MSDN.
Instead of calculating the coordinates for the lines, we can calculate the angle and then apply a rotation transform. The following code shows a function that draws one clock hand. The fAngle parameter gives the angle of the hand, in degrees.
void Scene::DrawClockHand(float fHandLength, float fAngle, float fStrokeWidth)
{
m_pRenderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(fAngle, m_ellipse.point)
);
// endPoint defines one end of the hand.
D2D_POINT_2F endPoint = D2D1::Point2F(
m_ellipse.point.x,
m_ellipse.point.y - (m_ellipse.radiusY * fHandLength)
);
// Draw a line from the center of the ellipse to endPoint.
m_pRenderTarget->DrawLine(
m_ellipse.point, endPoint, m_pStroke, fStrokeWidth);
}
This code draws a vertical line, starting from the center of the clock face and ending at the point endPoint. The line is rotated around the center of the ellipse by applying a rotation transform. The center point for the rotation is the center of ellipse that forms the clock face.
void Scene::RenderScene()
{
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::SkyBlue));
m_pRenderTarget->FillEllipse(m_ellipse, m_pFill);
m_pRenderTarget->DrawEllipse(m_ellipse, m_pStroke);
// Draw hands
SYSTEMTIME time;
GetLocalTime(&time);
// 60 minutes = 30 degrees, 1 minute = 0.5 degree
const float fHourAngle = (360.0f / 12) * (time.wHour) + (time.wMinute * 0.5f);
const float fMinuteAngle =(360.0f / 60) * (time.wMinute);
DrawClockHand(0.6f, fHourAngle, 6);
DrawClockHand(0.85f, fMinuteAngle, 4);
// Restore the identity transformation.
m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Identity() );
}
Refer: Drawing Clock Hands
Debug:
The whole code sample:
Direct2D Clock Sample

Related

Rotating an image using Borland C++ Builder and Windows API functions

I built this example to quickly rotate images 90 degrees but I always get a cut of the image on the sides. After many tests, unfortunately I still don't understand the cause of the problem.
void rotate()
{
Graphics::TBitmap *SrcBitmap = new Graphics::TBitmap;
Graphics::TBitmap *DestBitmap = new Graphics::TBitmap;
SrcBitmap->LoadFromFile("Crayon.bmp");
DestBitmap->Width=SrcBitmap->Width;
DestBitmap->Height=SrcBitmap->Height;
SetGraphicsMode(DestBitmap->Canvas->Handle, GM_ADVANCED);
double myangle = (double)(90.0 / 180.0) * 3.1415926;
int x0=SrcBitmap->Width/2;
int y0=SrcBitmap->Height/2;
double cx=x0 - cos(myangle)*x0 + sin(myangle)*y0;
double cy=y0 - cos(myangle)*y0 - sin(myangle)*x0;
xForm.eM11 = (FLOAT) cos(myangle);
xForm.eM12 = (FLOAT) sin(myangle);
xForm.eM21 = (FLOAT) -sin(myangle);
xForm.eM22 = (FLOAT) cos(myangle);
xForm.eDx = (FLOAT) cx;
xForm.eDy = (FLOAT) cy;
SetWorldTransform(DestBitmap->Canvas->Handle, &xForm);
BitBlt(DestBitmap->Canvas->Handle,
0,
0,
SrcBitmap->Width,
SrcBitmap->Height,
SrcBitmap->Canvas->Handle,
0,
0,
SRCCOPY);
DestBitmap->SaveToFile("Crayon2.bmp");
delete DestBitmap;
delete SrcBitmap;
}
If rotating the whole image, the width and height for destination image should be flipped:
DestBitmap->Width = SrcBitmap->Height;
DestBitmap->Height = SrcBitmap->Width;
The transform routine was centering the image based on original width/height. We want to adjust x/y position to push the starting point to left/top for BitBlt
int offset = (SrcBitmap->Width - SrcBitmap->Height) / 2;
BitBlt(DestBitmap->Canvas->Handle, offset, offset, SrcBitmap->Width, SrcBitmap->Height,
SrcBitmap->Canvas->Handle, 0, 0, SRCCOPY);
Once I had a similar problem.
I'm wanted to rotate two images around a common rotation point. But I couldn't do it with the standard function, because it doesn't allow a rotation point decentralized to the center.
Nevertheless I had made notes to the standard function at that time. Maybe they help you.
I'm remember that it was important that the size of the target image is correct! If a portrait image becomes a landscape image, the image becomes wider, therefore the BitBlt function must also specify the size of the target image.
Here my note to standard function.
Filling the xForm parameters was not quite the same for me as in your code snippet.
This was then the function I used to rotate around any center.

draw circle in XPS document API

How can I draw a circle with XPS document API?
I use following code, bit the circle is not drawn at all if start point and end point of circle is same. It draws "the arc of angle zero instead"..
XPS_POINT startPoint = { 500, 500};
hr = xpsFactory->CreateGeometryFigure(&startPoint, &figure);
XPS_SEGMENT_TYPE segmentTypes[1] = {
XPS_SEGMENT_TYPE_ARC_LARGE_COUNTERCLOCKWISE
};
// x - coordinate of the arc's end point.
// y - coordinate of the arc's end point.
// Length of the ellipse's radius along the x-axis.
// Length of the ellipse's radius along the y-axis.
// Rotation angle.
FLOAT segmentData[5] = {
startPoint.x, // if it is startPoint.x + 0.001, I see kind pf a circle
startPoint.y,
radius,
radius,
360
};
BOOL segmentStrokes[1] = {
TRUE// Yes, draw each of the segment arcs.
};
// Add the segment data to the figure.
hr = figure->SetSegments(
1,
5,
segmentTypes,
segmentData,
segmentStrokes);
hr = figure->SetIsClosed(TRUE);
hr = figure->SetIsFilled(TRUE);

Why drawString method does not seem to always start at the given coordinates?

In my code I cannot draw a String at precise coordinates. Its upper left corner does not start at the given coordinates but somewhere else. However if I draw a rectangle from the same given coordinates it is well placed. How on earth can this behaviour be possible ?
Here is my code I call in the beforeShow() method :
Image photoBase = fetchResourceFile().getImage("Voiture_4_3.jpg");
Image watermark = fetchResourceFile().getImage("Watermark.png");
f.setLayout(new LayeredLayout());
final Label drawing = new Label();
f.addComponent(drawing);
// Image mutable dans laquelle on va dessiner (fond blancpar défaut)
Image mutableImage = Image.createImage(photoBase.getWidth(), photoBase.getHeight());
// Paint all the stuff
paintAll(mutableImage.getGraphics(), photoBase, watermark, photoBase.getWidth(), photoBase.getHeight());
drawing.getUnselectedStyle().setBgImage(mutableImage);
drawing.getUnselectedStyle().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
// Save the graphics
// Save the image with the ImageIO class
long time = new Date().getTime();
OutputStream os;
try {
os = Storage.getInstance().createOutputStream("screenshot_" + Long.toString(time) + ".png");
ImageIO.getImageIO().save(mutableImage, os, ImageIO.FORMAT_PNG, 1.0f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
And the paintAll method
public void paintAll(Graphics g, Image background, Image watermark, int width, int height) {
// Full quality
float saveQuality = 1.0f;
// Create image as buffer
Image imageBuffer = Image.createImage(width, height, 0xffffff);
// Create graphics out of image object
Graphics imageGraphics = imageBuffer.getGraphics();
// Do your drawing operations on the graphics from the image
imageGraphics.drawImage(background, 0, 0);
imageGraphics.drawImage(watermark, 0, 0);
imageGraphics.setColor(0xFF0000);
// Upper left corner
imageGraphics.fillRect(0, 0, 10, 10);
// Lower right corner
imageGraphics.setColor(0x00FF00);
imageGraphics.fillRect(width - 10, height - 10, 10, 10);
imageGraphics.setColor(0xFF0000);
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(220, Font.STYLE_BOLD);
imageGraphics.setFont(f);
// Draw a string right below the M from Mercedes on the car windscreen (measured in Gimp)
int w = 0, h = 0;
imageGraphics.drawString("HelloWorld", w, h);
// Coin haut droit de la string
imageGraphics.setColor(0x0000FF);
imageGraphics.fillRect(w, h, 20, 20);
// Draw the complete image on your Graphics object g (the screen I guess)
g.drawImage(imageBuffer, 0, 0);
}
Result for w = 0, h = 0 (no apparent offset) :
Result for w = 841 , h = 610 (offset appears on both axis : there is an offset between the blue point near Mercedes M on the windscreen and the Hello World String)
EDIT1:
I also read this SO question for Android where it is advised to convert the dpi into pixel. Does it also applies in Codename One ? If so how can I do that ? I tried
Display.getInstance().convertToPixel(measureInMillimeterFromGimp)
without success (I used mm because the javadoc tells that dpi is roughly 1 mm)
Any help would be appreciated,
Cheers
Both g and imageGraphics are the same graphics created twice which might have some implications (not really sure)...
You also set the mutable image to the background of a style before you finished drawing it. I don't know if this will be the reason for the oddities you are seeing but I would suspect that code.
Inspired from Gabriel Hass' answer I finally made it work using another intermediate Image to only write the String at (0 ; 0) and then drawing this image on the the imageBuffer Image now at the right coordinates. It works but to my mind drawString(Image, Coordinates) should directly draw at the given coordinates, shouldn't it #Shai ?
Here is the method paintAll I used to solve my problem (beforeShow code hasn't changed) :
// Full quality
float saveQuality = 1.0f;
String mess = "HelloWorld";
// Create image as buffer
Image imageBuffer = Image.createImage(width, height, 0xffffff);
// Create graphics out of image object
Graphics imageGraphics = imageBuffer.getGraphics();
// Do your drawing operations on the graphics from the image
imageGraphics.drawImage(background, 0, 0);
imageGraphics.drawImage(watermark, 0, 0);
imageGraphics.setColor(0xFF0000);
// Upper left corner
imageGraphics.fillRect(0, 0, 10, 10);
// Lower right corner
imageGraphics.setColor(0x00FF00);
imageGraphics.fillRect(width - 10, height - 10, 10, 10);
// Create an intermediate image just with the message string (will be moved to the right coordinates later)
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(150, Font.STYLE_BOLD);
// Get the message dimensions
int messWidth = f.stringWidth(mess);
int messHeight = f.getHeight();
Image messageImageBuffer = Image.createImage(messWidth, messHeight, 0xffffff);
Graphics messageImageGraphics = messageImageBuffer.getGraphics();
messageImageGraphics.setColor(0xFF0000);
messageImageGraphics.setFont(f);
// Write the string at (0; 0)
messageImageGraphics.drawString(mess, 0, 0);
// Move the string to its final location right below the M from Mercedes on the car windscreen (measured in Gimp)
int w = 841, h = 610;
imageGraphics.drawImage(messageImageBuffer, w, h);
// This "point" is expected to be on the lower left corner of the M letter from Mercedes and on the upper left corner of the message string
imageGraphics.setColor(0x0000FF);
imageGraphics.fillRect(w, h, 20, 20);
// Draw the complete image on your Graphics object g
g.drawImage(imageBuffer, 0, 0);

How to draw a segment of a circle in Cocos2d-x?

Context
I try to draw pie chart for statistic in my game. I'm using Cocos2d-x ver.3.8.1. Size of the game is important, so I won't to use third-party frameworks to create pie charts.
Problem
I could not find any suitable method in Cocos2d-x for drawing part of the circle.
I tried to do
I tried to find a solution to this problem in Internet, but without success.
As is known, sector of a circle = triangle + segment. So, I tried to use the method drawSegment() from DrawNode also.
Although it has parameter radius ("The segment radius" written in API reference), radius affects only the thickness of the line.
drawSegment() method draw a simple line, the thickness of which is set by a method call.
Question
Please prompt me, how can I draw a segment or a sector of a circle in Cocos2d-x?
Any advice will be appreciated, thanks.
I think the one of the ways to draw a sector of a circle in Cocos2d-X is the way to use drawPolygon on DrawNode. I wrote little sample.
void drawSector(cocos2d::DrawNode* node, cocos2d::Vec2 origin, float radius, float angle_degree,
cocos2d::Color4F fillColor, float borderWidth, cocos2d::Color4F bordercolor,
unsigned int num_of_points = 100)
{
if (!node)
{
return;
}
const cocos2d::Vec2 start = origin + cocos2d::Vec2{radius, 0};
const auto angle_step = 2 * M_PI * angle_degree / 360.f / num_of_points;
std::vector<cocos2d::Point> circle;
circle.emplace_back(origin);
for (int i = 0; i <= num_of_points; i++)
{
auto rads = angle_step * i;
auto x = origin.x + radius * cosf(rads);
auto y = origin.y + radius * sinf(rads);
circle.emplace_back(x, y);
}
node->drawPolygon(circle.data(), circle.size(), fillColor, borderWidth, bordercolor);
}
This is the function to calculate the position of edge point of circle and draw polygon. If you want to use it, you need to call like following,
auto canvas = DrawNode::create();
drawSector(canvas, cocos2d::Vec2(400, 400), 100, 60, cocos2d::Color4F::GREEN, 2, cocos2d::Color4F::BLUE, 100);
this->addChild(triangle);
The result would be like this. I think the code will help your problem.

Box2D collision detection failure

I have recently began using Box2D version 2.1 in combination with Allegro5. Currently, I built a test with a ground and 4 boxes. 3 boxes are stacked, and the other one smashes causing them to flip. During this demonstration, I noticed got two glitches.
One is that creating a box in Box2D "SetAsBox( width, height )", only gives half the size of a normal box drawn to the screen using allegro. Example: In Box2D, I create a box the size of (15, 15). When I come to draw the shape using allegro, I must make an offset of -15 on the y, and scale the shape twice its size.
The other issue is during the collision detection while my boxes rotate due to impact. Most squares hit the ground, but some of them have an offset from the ground of its height making it floating.
Here is the code for making my boxes:
cBox2D::cBox2D( int width, int height ) {
// Note: In Box2D, 30 pixels = 1 meter
velocityIterations = 10;
positionIterations = 10;
worldGravity = 9.81f;
timeStep = ( 1.0f / 60.0f );
isBodySleep = false;
gravity.Set( 0.0f, worldGravity );
world = new b2World( gravity, isBodySleep );
groundBodyDef.position.Set( 0.0f, height ); // ground location
groundBody = world->CreateBody( &groundBodyDef );
groundBox.SetAsBox( width, 0.0f ); // Ground size
groundBody->CreateFixture( &groundBox, 0.0f );
}
cBox2D::~cBox2D( void ) {}
void cBox2D::makeSquare( int width, int height, int locX, int locY, float xVelocity, float yVelocity, float angle, float angleVelocity ) {
sSquare square;
square.bodyDef.type = b2_dynamicBody;
square.bodyDef.position.Set( locX, locY ); // Box location
square.bodyDef.angle = angle; // Box angle
square.bodyDef.angularVelocity = angleVelocity;
square.bodyDef.linearVelocity.Set( xVelocity, yVelocity ); // Box Velocity
square.body = world->CreateBody( &square.bodyDef );
square.dynamicBox.SetAsBox( width, height ); // Box size
square.fixtureDef.shape = &square.dynamicBox;
square.fixtureDef.density = 1.0f;
square.fixtureDef.friction = 0.3f;
square.fixtureDef.restitution = 0.0f; // Bouncyness
square.body->CreateFixture( &square.fixtureDef );
squareVec.push_back( square );
}
int cBox2D::getVecSize( void ) {
return squareVec.size();
}
b2Body* cBox2D::getSquareAt( int loc ) {
return squareVec.at( loc ).body;
}
void cBox2D::update( void ) {
world->Step(timeStep, velocityIterations, positionIterations);
world->ClearForces();
}
Edit:
Thank you Chris Burt-Brown for explaining the first issue to me, as for the second issue, It was a good idea, but it did not solve it. I tried both rounding methods you showed me.
Edit:
I think I found the answer to my second issue. Turns out that Allegro has a different coordinate system than OpenGL. As a result, instead of doing -gravity, I had to do +gravity which caused Box2D to become unstable and behave weird.
Edit:
My bad, I thought it was the issue, but turns out it did not change a thing.
It's actually SetAsBox(halfwidth, halfheight). I know it sounds weird but take a look inside SetAsBox. Passing in the parameters 15 and 15 will give a box with corners (-15,-15) and (15,15) i.e. a box of size 30x30.
I think it's intended as an optimisation, but it's a pretty silly one.
I don't know what's causing your other problem, but when you draw the boxes with Allegro, try seeing if it's fixed when you round the coordinates. (If that doesn't work, try ceil.)