how to draw a 3d cylinder in opengl
Drawing a Cylinder |
This little project arose every bit office of a discussion in the newsgroup about "How tin can I describe a cylinder?" As far equally I can tell, the poster really needed to use something like OpenGL or Direct3D to draw the image of a cylinder, merely it got me thinking about a simpler problem, how to create a cylindrical image, which I might want to utilise as, for example, a histogram element. As well, I was trapped on an plane going to San Diego, and it was either write code or read science fiction, so this seemed a flake more productive.
The Cylinder program is shown here. Information technology has some sliders that control the width and height of the cylinder (expressed as a percentage of the total cartoon surface area), the bending of the view, and the angle of the light. There are a couple buttons to specify the color scheme used for the nighttime and lite parts of the drawing.
Other than a tabular array, which takes 25 lines, the actual code that does the cylinder drawing is less than lxx lines of declarations and computations. So the whole thing is nether 100 lines of lawmaking. The remaining 3,700 lines are simple methods that set/get the parameters to the drawing, and provide all the support, pretty buttons, fancy slider controls, and like parts of the interaction. Nearly 1300 lines of source lawmaking provide the basic interaction. A lot of these bargain with the fact that I chose to make the dialog resizable, and the controls float around on it (this is the closest I've come up to going to the effort to get a command geometry manager package installed).
I worked out the basic highlighting technique using my GradientFill Explorer; it takes 4 points to specify the cylinder fill: from dark to low-cal, and from calorie-free to dark.
The trouble with the elementary approach is that it produces a rectangle with a imitation 3-D effect:
This is not entirely convincing. I really had in mind something more along the lines of a cylinder grade
So the question was, how hard would this be to describe using only straight GDI functions, non requiring whatsoever 3-D support?
The answer is obviously "yes" considering I accept demonstrably done it. However, I was on my own to perform all the geometric computations required to make this so. As well, I did non endeavour to "rotate" the cylinder itself, or allow anything to be within it, or attempt to requite a perspective drawing as the viewing bending changed. So I accept made a number of simplifying assumptions here that permit me to do a number of simpler drawing techniques than would exist required to generally show a 3-D cylinder shape from whatever angle, or with perspective.
The respond turned out to be: not hard at all. But in that location are a few tricks I needed to do to make it look simply correct. For example, I wanted to change the bending of the low-cal from dead-on-straight to allow whatsoever angle from -90� to +90�:
-xc� | -45� | -15� | 0� | +xx� | +40� | +60� | +90� |
This is not entirely obvious how to get the best-looking result. My first endeavor, for case, just did a double-gradient-fill issue with the left and right endpoints forming a 4-strip slope fill; this is the 0 effect. The problem was that every bit I changed the angle, the left terminate (moving to the left) or right end (moving to the right) remained at the aforementioned intensity on the edge, which was the dark colour. This was not a satisfactory illusion. The illustration beneath is captured from the GradientFill Explorer.
Information technology you wait at this, it does not await like the prototype is a smooth cylinder; our feel tells us that the dark edge must be the same distance away from our eyes on each side, and therefore it looks like the curve suddenly becomes tighter later on the highlighted area.
It turns out that the right pull a fast one on was to compute the distance edge-to-height on the "shadowed" side and so draw an prototype of the same width, peak-to-new-edige, on the "lighted" side, and clip this to the size of the actual cylinder. Compare the algorithm used past the naive gradient to the "smart" gradient, again, equally shown by the Gradient Fill Explorer:
The algorithm was
- Compute the offset of the peak of the brightness
- Compute the actual left or correct extension to get the "smart gradient make full" effect described above
- Compute the size of the endcap ellipse for each end of the cylinder
- Create a clipping region that runs from the "apartment" terminate to the "rounded" finish
- Perform the gradient fill, clipped by the clipping region
- Depict the visible endcap
CPoint pt(-(width / 2), -(height / ii) ); double theta = DEGREES_TO_RADIANS((double)lightAngle); double xoffset = ((double)width / two.0) * sin(theta); // The altitude dx is the distance from the center line to // the point of tiptop brightness. Notation that dx could be negative int dx = (int)xoffset; // Compute the x-coordinate of the meridian of the effulgence int peak = pt.x + (width / 2) + dx; // Assume for the moment that the width we are going to draw // is the actual width of the image int left = pt.x; int correct = pt.x + width; // Now adjust either the right or the left then that the new // point places the elevation in the center of the area computed, // that is, top == (right - left) / 2 // afterward nosotros conform left and correct if(dx < 0) left = peak - (correct - summit); else right = peak + (peak - left); |
// Compute the size of the end cap. Start past assuming // both end caps are "flat", that is, of 0 height // These two elements will be used to draw the ellipse // for each finish CRect top (pt.x, pt.y, pt.x + width, pt.y); CRect bottom(pt.x, pt.y + height, pt.ten + width, pt.y + peak); // At present "rotate" the finish cap past the amount based on the heart angle double rotate = DEGREES_TO_RADIANS(eyeAngle); // Compute the size of the upper curve and the size of the // lower curve of the stop caps. Remember that in MM_TEXT // mode (the coordinates we are using) the y-centrality // increases downward int dy = (int) (fabs((double)(width / ii) * sin(rotate))); |
// Create a region based on the trunk dimenions CRgn rectRgn; rectRgn.CreateRectRgnIndirect(&bodyRect); // Create a bounding rectangle for the endcap CRect curveRect(pt.10, -dy, pt.x + width, dy); // Compute the total size of the "fill" rectangle, which is // gradient-filled. If the center is "higher up" the centre, we // will see the top as an ellipse and the bottom as a curve // If the eye is "below" the eye, nosotros will see the top // as a curve and the bottom as an ellipse if(eyeAngle > 0) { /* has top */ top.top = pt.y - dy; peak.lesser = pt.y + dy; curveRect.top += pt.y + summit; curveRect.bottom += pt.y + height; } /* has meridian */ else { /* has bottom */ bottom.superlative = pt.y + height - dy; bottom.bottom = pt.y + height + dy; curveRect.superlative += pt.y; curveRect.lesser += pt.y; } /* has bottom */ // Create a clipping region for the curve. This will exist // used to "truncate" the rectangular area we fill to // simply show the curved part CRgn curveRgn; // In GDI, the endpoint is up-to-merely-not-including the // maximum right side. The problem with this is that // the finish cap will come out a chip short, so add 1 to // compensate for this feature if(::GetGraphicsMode(dc) != GM_ADVANCED) curveRect.correct++; // allow for standard off-by-one issue // Now create a region that is an ellipse curveRgn.CreateEllipticRgnIndirect(&curveRect); // "OR" the region into the electric current body region to extend // it to include the elliptical area. The parts of // the ellipse that overlap the rectangle don't matter; // the parts that extend beyond the body region will give // us the bottom curve or top curve that we need rectRgn.CombineRgn(&rectRgn, &curveRgn, RGN_OR); |
TRIVERTEX idiot box[4] = { { left, pt.y - dy, MAKEWORD(0, GetRValue(darkColor)), MAKEWORD(0, GetGValue(darkColor)), MAKEWORD(0, GetBValue(darkColor)), MAKEWORD(0, 255)}, { summit, pt.y + meridian + dy, MAKEWORD(0, GetRValue(lightColor)), MAKEWORD(0, GetGValue(lightColor)), MAKEWORD(0, GetBValue(lightColor)), MAKEWORD(0, 255)}, { summit, pt.y - dy, MAKEWORD(0, GetRValue(lightColor)), MAKEWORD(0, GetGValue(lightColor)), MAKEWORD(0, GetBValue(lightColor)), MAKEWORD(0, 255)}, { right, pt.y + height + dy, MAKEWORD(0, GetRValue(darkColor)), MAKEWORD(0, GetGValue(darkColor)), MAKEWORD(0, GetBValue(darkColor)), MAKEWORD(0, 255)}, }; GRADIENT_RECT gr[two] = { {0, 1}, {2, 3}, }; // Select the new clipping region and gradient-fill the // area. The clipping region limits the gradient make full // so that the extended "spillover" of the left or right // will not actually draw { /* draw gradient */ int save2 = dc.SaveDC(); //****************************************************************** // This transformation merely works for straight GDI without // GM_ADVANCED graphics mode being set // Come across the avant-garde transformation technique for general // transformation matrices CPoint offset = dc.GetViewportOrg(); clipRgn.CopyRgn(&rectRgn); clipRgn.OffsetRgn(start); //****************************************************************** dc.SelectClipRgn(&clipRgn); BOOL result = dc.GradientFill(tv, iv, gr, 2, GRADIENT_FILL_RECT_H); dc.RestoreDC(save2); } /* depict slope */ |
| // Create a brush which is the "boilerplate" color for the end cap COLORREF endcap = RGB( (GetRValue(darkColor) + GetRValue(lightColor)) / 2, (GetGValue(darkColor) + GetGValue(lightColor)) / 2, (GetBValue(darkColor) + GetBValue(lightColor)) / two); CBrush ecb(endcap); dc.SelectObject(&ecb); if(!peak.IsRectEmpty()) dc.Ellipse(&top); if(!bottom.IsRectEmpty()) dc.Ellipse(&bottom); |
The basic code was quite simple, just I wanted to be able to rotate the cylinder also. This became a lot more interesting, and a lot more challenging. In GM_ADVANCED manner, you can use a transformation matrix to perform operations such as rotation, kickoff, and shear. A transformation matrix is formally a ix-chemical element matrix, and matrix operations such as matrix multiply tin can use to sequences of matrices. However, the matrix is always represented equally
Because the last column is always the constants 0,0,1, the transformation matrix only needs the 6-tuple
When this matrix is applied to a point (ten,y), the computation is
The following are classic parameter values for the matrix
|
|
|
|
Deportation by the values tx ty | Scaling in 10,y by scaling factors sx, southy | Rotation counterclockwise by q | Shear by h10 .hy |
Clicking the Enable Transformation Matrix check box enables the matrix controls.
Cylinder Demo Reference
Control | Function |
Selects the edge and heart colors for the cylinder. These two buttons together are gradient-filled with the selected colors. | |
"Zeroes" the Width, Summit, Viewport, and Low-cal Source controls to their startup positions | |
Copies the cylinder and its background to the clipboard | |
Enables the use of GM_ADVANCED manner and a transformation matrix. The transformation matrix controls volition appear | |
Establishes the width as a pct of the image area. The range is 0% to 100% | |
Establishes the angle of the lighting. The range is in degrees, from -90� to +xc� | |
Establishes the height every bit a per centum of the image area. The range is 0% to 100% | |
Establishes the viewpoint of the eye. The range is -45� to +45� | |
Plugs values into the transformation matrix for a rotational transformation. Note that using this control volition replace the values for Meleven, M12, Chiliad21 and One thousand22. Information technology volition leave Dx and Dy unchanged. The angle of rotation is displayed at the lesser. The range of rotation is from -90� to +xc�. | |
Replaces the matrix with the identity transformation, | |
These controls are the 6 control points of the matrix
|
Using Matrix Transformations
A transformation matrix is capable of much more than, for example, shear operations
| ||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
The interesting affair about this experiment was that, while it was clearly more complex than it would have to describe a cylinder using something like OpenGL, it was not impossible, or, once the basic ideas were established, even particularly difficult. The play a trick on was in figuring out the sequence of operations.
The but trick was the realization that the documentation of CDC::SelectClipRgn was incomplete and misleading. It states that the coordinates are in units of device infinite, but in fact this is not true. The coordinates of the clipping region is in the coordinate space of the device space. Once that realization was made the fob was to but create a transformed clipping region. The ExtCreateRegion, which does not accept a method of the CDC class, will practice this. So all I had to practice was
- Get the current transformation matrix
- Normalize it to an offset of (0,0)
- Obtain the current clipping region as already computed (without the transform)
- Create the transformed clipping region, which will exist normalized to 0,0
- Offset it to the position of the current viewport origin, and offset information technology over again by the offset established for the transformation
This is shown in the lawmaking beneath. Note that at that place is no need to do this complex performance if the GM_ADVANCED graphics manner is used, so the "original" code is used.
// Select the new clipping region and gradient-fill up the // area. The clipping region limits the gradient fill // so that the extended "spillover" of the left or right // will not actually draw { /* draw gradient */ int save2 = dc.SaveDC(); //********************************************************************************* // This lawmaking creates a transformed clipping region using the electric current transformation // matrix. It is a generalization of the "straight GDI" clipping region // computation if(::GetGraphicsMode(dc) == GM_ADVANCED) { /* may demand transform */ XFORM Matrix; ::GetWorldTransform(dc, &Matrix); // +- -+ // | M11 M12 | // | M21 M22 | // | dx dy | // +- -+ CPoint originalOffset( (int)Matrix.eDx, (int)Matrix.eDy); // To brand this piece of work nether rotation we accept to normalize it // to (0,0) so it rotates without displacements Matrix.eDx = 0.0f; Matrix.eDy = 0.0f; // +- -+ // | M11 M12 | // | M21 M22 | // | 0 0 | // +- -+ // Now create a new region transformed past the matrix CRegionData data; data.Fix(clipRgn); HRGN xformrgn = ::ExtCreateRegion(&Matrix, information.GetSize(), data.GetData()); clipRgn.DeleteObject(); clipRgn.Attach(xformrgn); // Now slide the region over so it overlaps the area of the cylinder CPoint outset = dc.GetViewportOrg(); clipRgn.OffsetRgn(start); clipRgn.OffsetRgn(originalOffset); } /* may need transform */ else { /* non-transform */ // This is the "directly GDI" lawmaking CPoint offset = dc.GetViewportOrg(); clipRgn.CopyRgn(&rectRgn); clipRgn.OffsetRgn(showtime); } /* not-transform */ //*********************************************************************************** |
How to call it
The parameters of the Cylinder form are set with the methods of the class. There would be ane instance of a Cylinder object for each cylinder to be fatigued.
Cylinder::Cylinder() | Constructor The default values are
| ||||||||||||||||
void Draw(CDC & dc); | Draws the cylinder object on the DC, based on the parameters which have been set. The constructor sets specific defaults. | ||||||||||||||||
void GetColor(COLORREF & light, COLORREF & dark); | Retrieves the light and dark highlighting colors of the cylinder Run into As well: GetDarkColor, GetLightColor, SetColor, SetDarkColor, SetLightColor | ||||||||||||||||
COLORREF GetDarkColor(); | Retrieves the dark highlighting colour of the cylinder Come across Also: GetColor, GetLightColor, SetColor, SetDarkColor, SetLightColor | ||||||||||||||||
int GetEyeAngle(); | Retrieves the vertical viewing angle Meet As well: SetEyeAngle | ||||||||||||||||
int GetHeight(); | Retrieves the peak of the cylinder, in logical units. See Also: SetHeight | ||||||||||||||||
int GetLightingAngle(); | Retrieves the angle of the light shining on the cylinder See As well: SetLightingAngle | ||||||||||||||||
COLORREF GetLightColor(); | Retrieves the low-cal highlighting colour of the cylinder. See Likewise: GetColor, GetDarkColor, SetColor, SetDarkColor, SetLightColor | ||||||||||||||||
BOOL GetMatrix(XFORM & M); | If there is an agile matrix selected, stores the parameters in the matrix One thousand, and returns TRUE. If the SetMatrix was for Nothing, will non change M and will return Simulated See Also: SetMatrix | ||||||||||||||||
CPoint GetPos(); | Retrieves the position of the centroid of the cylinder in logical coordinates. Encounter Also: SetPos | ||||||||||||||||
CSize GetSize(); | Retrieves the electric current width and superlative of the cylinder in logical units. See Also: GetHeight, GetWidth, SetHeight, SetSize, SetWidth | ||||||||||||||||
int GetWidth(); | Retrieves the electric current width of the cylinder in logical units. Run into Also: GetHeight, GetSize, SetHeight, SetSize, SetWidth | ||||||||||||||||
void SetColor(COLORREF light, COLORREF nighttime); | Sets the two colors used for the lighting effects. If this is not called, a default color is used. See Also: GetColor, GetDarkColor, GetLightColor, SetDarkColor, SetLightColor | ||||||||||||||||
void SetDarkColor(COLORREF c); | Sets the dark color used for the lighting effects. If this is not chosen, a default color is used. See Also: GetColor, GetDarkColor, GetLightColor, SetColor, SetLightColor | ||||||||||||||||
void SetEyeAngle(int degrees); | Sets the viewing angle, in degrees. If this is non called, a default value is used. The viewing angle is limited to the range -45� to +45�. See As well: GetEyeAngle | ||||||||||||||||
void SetHeight(int height); | Sets the cylinder height in logical units. If this is not called, a default value is used. See Also: GetHeight, GetSize, GetWidth, SetSize, SetWidth | ||||||||||||||||
void SetLightingAngle(int degrees); | Sets the lighting bending, in degrees. If this is not chosen, a default value is used. The lighting angle is limited to the range -ninety� to +90�. Come across Also: GetLightingAngle | ||||||||||||||||
void SetLightColor(COLORREF c); | Sets the calorie-free color for the highlighting. If this is not called, a default colour is used. Encounter Also: GetColor, GetDarkColor, GetLightColor, SetColor, SetDarkColor | ||||||||||||||||
void SetMatrix(XFORM * M); | Sets the transformation matrix to be used for drawing the cylinder. If the value is Zippo, the cylinder can only be drawn vertically using the base GDI functionality. Run across Also: GetMatrix | ||||||||||||||||
void SetPos(CPoint pt); void SetPos(int x, int y); | Sets the centroid position for the cylinder. If this is not called, a default value is used. Come across Besides: GetPos | ||||||||||||||||
void SetSize(CSize sz); | Sets the width and meridian of the cylinder. If this is not called, a default value is used. See Also: GetHeight, GetSize, GetWidth, SetHeight, SetWidth | ||||||||||||||||
void SetWidth(int width); | Sets the width of the cylinder. If this is not called, a default value is used. See Too: GetHeight, GetSize, GetWidth, SetHeight, SetSize |
Why information technology works
There are a lot of simplifying assumptions that apply hither that reduce the problem to something manageable in straight GDI
- There is no perspective drawing
- There is no need to rotate in the ten-y plane
Changing any of these assumptions can outcome in a slightly-more-hard to nearly-impossible state of affairs.
Date | Change |
30-Mar-08 | Hans-J. Ude's suggestion for flicker-costless lawmaking reduction was added. I was only feeling lazy, and then I give thanks him for his contribution. |
Special cheers besides to "Nivel" who converted the .wmf files to .png files for wider browser compatibility. |
The views expressed in these essays are those of the writer, and in no way stand for, nor are they endorsed by, Microsoft.
Send post to newcomer@flounder.com with questions or comments almost this web site.
Copyright � 2008 FlounderCraft, Ltd., All Rights Reserved.
Concluding modified: May fourteen, 2011
Source: http://www.flounder.com/cylinder.htm
0 Response to "how to draw a 3d cylinder in opengl"
Post a Comment