I have a ViewPort3D element in a multi-device application form that being filled with a large number of TRectangle3D elements (anywhere from 1 to 10000) with LightMaterialSource applied to them, which all need to be rendered dynamically since I'm also rotating the camera using the following procedure:
procedure TForm3.Viewport3D1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
var
I: IViewport3D;
begin
if ssRight in shift then
begin
I:=ViewPort3D1;
with tdummy(I.CurrentCamera.Parent) do RotationAngle.X:=RotAng.X - Y;
with tdummy(I.CurrentCamera.Parent.Parent) do RotationAngle.Y:=RotAng.Y + X;
end;
end;
However the performance of the ViewPort3D begins to drop noticeably when the number of rectangles rendered approaches at least several dozen. The camera rotation gets slower and more unresponsive the more rectangles are added to the viewport up to the point of becoming a slideshow.
Is there a way to improve performance of the ViewPort3D without deleting said rectangles?
I've tried using setting the Multisample property to "none": ViewPort3d1.Context.SetMultisample(TMultisample.None) as well as removing MaterialSource from all the rectangles. While it did help a little with the performance, it didn't solve the problem entirely.
For 10k in my tests
Context.Draw/Fill.cube
Drawing in the render event is much more efficient, but the performance drops significantly when the camera angle is changed.
TRectangle3D Creating 10k pieces turns into serious performance problems after 2k created and showed on windows.
if visible=false is set after TRectangle3D create, rendering is completed at very high speed,
however, when visible=true, there is a serious performance degradation when the camera angle is changed.
As far as I can see, the reason for the slowdown is the operations on the CPU, that is, the gpu is not the part that slows down here, but when I examine the codes, there is a constant high amount of event type message notifications going to on mousemove to objects.
my suggestion is if a lot of visible objects are going to be used,
if objects are out of camera view, hide objects(visible=false), hide objects with a loop at each mousemove event, see if objects are in camera area or not, this will help performance.
There is not only 10k object problem here, it has many shortcomings.
basically this one can be used as a simple drawing 3d engine for simple jobs.
As far as I know, in Java, just like the viewport3d object, we can add the unity engine graphical window to a form in a java application,
I don't know if unity engine graphics window can be added to delphi,
u can request the support from embercadero, but for advanced graphics it would be more logical to use engines like unreal unity proffesional optimized engines.
Each high level fmx 3d object make a "drawcall." (wich reprocess all, from cpu (mesh preparation) to gpu (for diplay)
-> You always have to minimize drawcall count.
So, displaying 10000 FMX rectangles is never a solution. :)
You have to make only 1 Tmesh descendant, witch will draw your 10000 "hand-made" rectangles in one call.
Please see TMesh.Data (data.points and data.triangleindice) to undestand how to draw a 3d object in direct mode. Or, more simply, see how "TPlane" (in source) is built.
As a general plan, and as a basic "3d making" approach, making "complex" 3d FMX object is possible, but you have to deal with vertices/indexes to draw much possible things in one call.
As an exemple, here a is the code to realize it :
put TMesh on your viewport, and call this proc :
Procedure PopulateMesh_RectangleMap(aMesh : TMesh; const xCount : integer = 100; const yCount : integer=100; const rectWidth : single = 2.0; const rectHeight : single= 1.0);
var lv,li : integer;
lsx, lsy, xpos, ypos : single;
i,j : integer;
a,b,c,d : TPoint3d; //4 corner of a rect.
begin
Assert(assigned(aMesh));
lsx := rectWidth;
lsy := rectHeight;
li := 0;
lv := 0;
//mem. allocation.
aMesh.Data.Clear;
aMesh.Data.VertexBuffer.Length := 4 * xCount * yCount; //4 vertices by rect, need k*k rects.
aMesh.Data.IndexBuffer.Length := 6 * xCount * yCount; // 6 indices for 2 triangles desc (to make 1 rect) for k*k rects.
for i := 0 to xcount-1 do
for j := 0 to ycount-1 do begin
xpos := -xcount/2 + i + i*lsx;
ypos := -ycount/2 + j + j*lsy;
a := point3d(xpos - lsx/2,ypos - lsy/2,0);
b := point3d(xpos + lsx/2,ypos - lsy/2,0);
c := point3d(xpos + lsx/2,ypos + lsy/2,0);
d := point3d(xpos - lsx/2,ypos + lsy/2,0);
aMesh.Data.VertexBuffer.Vertices[li+0] := a;
aMesh.Data.VertexBuffer.Vertices[li+1] := b;
aMesh.Data.VertexBuffer.Vertices[li+2] := c;
aMesh.Data.VertexBuffer.Vertices[li+3] := d;
aMesh.Data.IndexBuffer.Indices[lv+0] := li+0;
aMesh.Data.IndexBuffer.Indices[lv+1] := li+1;
aMesh.Data.IndexBuffer.Indices[lv+2] := li+2;
aMesh.Data.IndexBuffer.Indices[lv+3] := li+2;
aMesh.Data.IndexBuffer.Indices[lv+4] := li+3;
aMesh.Data.IndexBuffer.Indices[lv+5] := li+0;
inc(li,4);
inc(lv,6);
end;
aMesh.Data.BoundingBoxNeedsUpdate; //We touch data. Update Mesh container.
aMesh.Width := lsx*3; //keep proportion.
aMesh.Height := lsy*3;
aMesh.Repaint;
end;
I'm trying to make a midi-editor in processing and I need to have rectangular cells that you can click on to add a note there.
So far I've done googling but haven't found a result. What happens is when you click the further down the less accurate it gets.
I don't know what is going wrong and I've had this problem in the past but couldn't fix it.
Note: The cells are not square.
Example processing code to snap the mouse coordinates to the center of the nearest grid cell (where cell dimensions are defined by rectangle width and height).
int w = 48; // rectangle width
int h = 24; // rectangle height
int snapX = round(mouseX / w) * w + w/2; // '+ w/2' is offset to center of cell
int snapY = round(mouseY / h) * h + h/2; // '+ h/2' is offset to center of cell
Mapbox provides Global elevation data with height data encoded in PNG image. Height is decoded by height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1). Details are in https://www.mapbox.com/blog/terrain-rgb/.
I want to import the height data to generate terrains in Unity3D.
Texture2D dem = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/dem/12/12_3417_1536.png", typeof(Texture2D));
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
{
Color c = dem.GetPixel(i, j);
float R = c.r*255;
float G = c.g*255;
float B = c.b*255;
array[i, j] = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1f);
}
Here I set a break point and the rgba value of the first pixel is RGBA(0.000, 0.592, 0.718, 1.000). c.r is 0. The height is incorrect as this point represent the height of somewhere on a mountain.
Then I open the image in Photoshop and get RGB of the first pixel: R=1,G=152,B=179.
I write a test program in C#.
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap("12_3417_1536.png");
Color a = bitmap.GetPixel(0, 0);
It shows Color a is (R,G,B,A)=(1,147,249,255)
Here is the image I test:
https://api.mapbox.com/v4/mapbox.terrain-rgb/12/3417/1536.pngraw?access_token=pk.eyJ1Ijoib2xlb3RpZ2VyIiwiYSI6ImZ2cllZQ3cifQ.2yDE9wUcfO_BLiinccfOKg
Why I got different RGBA value with different method? Which one is correct?
According to the comments below, different read order and compressed data in unity may result in different value of the rgba of pixel at (0,0).
Now I want to focus on----How to convert the rgba(0~1) to RGBA(0~255)?
r_ps=r_unity*255? But how can I explain r=0 in unity and r=1 in PS of pixel at (0,0)
?
Try disabling compression from the texture's import settings in Unity (No compression). Alternatively, if you fetch the data at runtime, you can use Texture.LoadBytes() to avoid compression artifacts.
I will assume you are using the same picture and that there aren't two 12_3417_1536.png files in separate folders.
Each of these functions has a different concept of which pixel is at (0,0). Not sure what you mean by "first" pixel when you tested with photoshop, but Texture coordinates in unity start at lower left corner.
When I tested the lower left corner pixel using paint, I got the same value as you did with photoshop. However, if you test the upper left corner, you get (1,147,249,255) which is the result bitmap.GetPixel returns.
The unity values that you're getting seem to be way off. Try calling dem.GetPixel(0,0) so that you're sure you're analyzing the simplest case.
First, please note, that this question is not a duplicate of these: 1st , 2nd , and 3rd.
I am using delphi and openCV, but I am looking for an algorithm, a solution regardless of the language.
For the purpose of a precise image analysis, I need to check for changes in pixel intensity in circular areas. So I read pixel values on a circumference of continuously growing circle. To be able to do that, I of course need to know coordinates of the pixels.
The best solution I found is y:= Round(centerY + radius * sin(angle)), x:= Round(centerX + radius * cos(angle)), while because counting with only 360 degrees is hardly enough, when the radius of the circle is larger, than circa 60px, the angle is being counted like this angle:= angle + (360 / (2 * 3.14 * currentRadius)) -> I sweep through every value from 0 to 360, while the value is being incremented by a fraction of 360/circumference of the circle in pixels. But this approach is not very precise. The larger the circle, the smaller the fraction of the angle needs to be and the precission suffers from the inaccuracy of Pi, plus the rounding.
If I use the mentioned method, and try to draw the counted pixels with this code:
centerX:= 1700;
centerY:= 1200;
maxRadius:= 500;
for currentRadius:= 80 to maxRadius do
begin
angle:= 0;
while angle < 360 do
begin
xI:= Round(centerX + currentRadius * cos(angle));
yI:= Round(centerY + currentRadius * sin(angle));
angle:= angle + (360 / (2 * 3.14 * currentRadius));
//this is openCV function, to test the code, you can use anything, that will draw a dot...
cvLine(image,cvPoint(xI,yI),cvPoint(xI,yI),CV_RGB(0, 255, 0));
end;
end;
the result is this:
It is not bad, but taking into account, that rougly a third of all pixels in the circular area are black, you realize, that a lot of pixels has been "skipped". Plus looking closely on the edge of the last circle, there is clearly visible, that some dots are off the actual circumference - another result of the inaccuracy...
I could possibly use a formula (x - xorig)^2 + (y - yorig)^2 = r^2 to check every possible pixel in a rectangular area around the center, slightly bigger, than a diameter of the circle, if it does, or does't fall onto the cirle's circumference. But that would be very slow to repeat it all the time, as the circle grows.
Is there something, that could be done better? Could anyone help me to improve this? I don't insist on anything from my solution at all, and will accept any other solution, as long as it gives the desired results => let me read values of all (or the vast majority - 95%+) pixels on a circumference of a circle with given center and radius. The faster, the better...
1) Build a list of pixels of the smallest radius circumference. It is enough to
keep the first octant (range 0..Pi/4 in the 1st quadrant of coordinate system) of circle, and get symmetric points with reflections.
You can use, for example, Bresenham circle algorithm or just circle equation.
2) For the next iteration walk through all coordinates in the list (use right one, if there are two points with the same Y value) and check whether right neighbor (or two neighbors!) lies inside the next radius. For the last point check also top, right-top neighbor (at Pi/4 diagonal).
Insert good neighbors (one or two) into the next coordinate list.
Example for Y=5.
R=8 X=5,6 //note that (5,5) point is not inside r=7 circle
R=9 X=7
R=10 X=8
R=11 X=9
R=12 X=10
R=13 X=11,12 //!
R=14 X=13
With this approach you will use all the pixels in maximal radius circle without gaps, and checking process for list generation is rather fast.
Edit:
Code implements slightly another approach, it uses lower line pixel limit to built upper line.
It generates circles in given range, paints them to psychedelic colors. All math is in integers, no floats, no trigonometric functions! Pixels are used only for demonstration purposes.
procedure TForm1.Button16Click(Sender: TObject);
procedure FillCircles(CX, CY, RMin, RMax: Integer);
//control painting, slow due to Pixels using
procedure PaintPixels(XX, YY, rad: Integer);
var
Color: TColor;
r, g, B: Byte;
begin
g := (rad mod 16) * 16;
r := (rad mod 7) * 42;
B := (rad mod 11) * 25;
Color := RGB(r, g, B);
// Memo1.Lines.Add(Format('%d %d %d', [rad, XX, YY]));
Canvas.Pixels[CX + XX, CY + YY] := Color;
Canvas.Pixels[CX - YY, CY + XX] := Color;
Canvas.Pixels[CX - XX, CY - YY] := Color;
Canvas.Pixels[CX + YY, CY - XX] := Color;
if XX <> YY then begin
Canvas.Pixels[CX + YY, CY + XX] := Color;
Canvas.Pixels[CX - XX, CY + YY] := Color;
Canvas.Pixels[CX - YY, CY - XX] := Color;
Canvas.Pixels[CX + XX, CY - YY] := Color;
end;
end;
var
Pts: array of array [0 .. 1] of Integer;
iR, iY, SqD, SqrLast, SqrCurr, MX, LX, cnt: Integer;
begin
SetLength(Pts, RMax);
for iR := RMin to RMax do begin
SqrLast := Sqr(iR - 1) + 1;
SqrCurr := Sqr(iR);
LX := iR; // the most left X to check
for iY := 0 to RMax do begin
cnt := 0;
Pts[iY, 1] := 0; // no second point at this Y-line
for MX := LX to LX + 1 do begin
SqD := MX * MX + iY * iY;
if InRange(SqD, SqrLast, SqrCurr) then begin
Pts[iY, cnt] := MX;
Inc(cnt);
end;
end;
PaintPixels(Pts[iY, 0], iY, iR);
if cnt = 2 then
PaintPixels(Pts[iY, 1], iY, iR);
LX := Pts[iY, 0] - 1; // update left limit
if LX < iY then // angle Pi/4 is reached
Break;
end;
end;
// here Pts contains all point coordinates for current iR radius
//if list is not needed, remove Pts, just use PaintPixels-like output
end;
begin
FillCircles(100, 100, 10, 100);
//enlarge your first quadrant to check for missed points
StretchBlt(Canvas.Handle, 0, 200, 800, 800, Canvas.Handle, 100, 100, 100,
100, SRCCOPY);
end;
If you want to make your code faster, don't call trigonometric functions inside the inner loop, increment sin(angle) and cos(angle) using
sin(n*step)=sin((n-1)*step)*cos(step)+sin(step)*cos((n-1)*step)
cos(n*step)=cos((n-1)*step)*cos(step)-sin(step)*sin((n-1)*step)
that is
...
for currentRadius:= 80 to maxRadius do
begin
sinangle:= 0;
cosangle:= 1;
step:= 1 / currentRadius; // ?
sinstep:= sin(step);
cosstep:= cos(step);
while {? } do
begin
xI:= Round(centerX + currentRadius * cosangle);
yI:= Round(centerY + currentRadius * sinangle);
newsin:= sinangle*cosstep + sinstep*cosangle;
newcos:= cosangle*cosstep - sinstep*sinangle;
sinangle:= newsin;
cosangle:= newcos;
...
end;
end;
First of all: you want all the points on a circle circumference. If you use any (good) algorithm, also some built-in circle function, you get indeed all the points, since the circumference is connected.
What your picture shows, there are holes between neighbour circles, say r=100 and r=101. This is so for circumference drawing functions.
Now if you want that the pixels in your pixel set to cover all the pixels with incrementing radii, you can simply use following approach:
Build a set of filled circle pixels, say r = 101
Build a set of filled circle pixel with r = 100
Exclude set 2 from set 1
Filled circle algorithm is generally more efficient and simpler than connected circumference so you'll not loose much performance.
So you get a circumference which is slightly thicker than 1 px, but this set will surely cover the surface with growing radii without any holes. But it can also happen that the set built in such a way has overlapping pixels with previous set (r-1), so you'd know it better if you test it.
PS: Also it is not clear how any trigonometric functions appear in your code. I don't know any effective circle algorithm which use anything other than square root.
Why don't you simply use more digits for Pi and stop rounding to improve accuracy?
Further I suggest you use subpixel coordinates to get more accurate intensity values if you can afford the interpolation.
It's also very uncommon to use degrees in calculations. I highly recommend using radians. Not sure which functions you use here but Delphi's cos and sin seem to expect radians!
Can I directly modify per-pixel alpha data for TPngImage between loading it from somewhere and drawing it somewhere? If so, how? Thanks.
Yes, I think that is easy.
For example, this code will set the opacity to zero (that is, the transparency to 100 %) on every pixel in the upper half of the image:
var
png: TPNGImage;
sl: PByteArray;
...
for y := 0 to png.Height div 2 do
begin
sl := png.AlphaScanline[y];
FillChar(sl^, png.Width, 0);
end;
This will make a linear gradient alpha channel, from full transparency (alpha = 0) to full opacity (alpha = 255) from left to right:
for y := 0 to png.Height do
begin
sl := png.AlphaScanline[y];
for x := 0 to png.Width - 1 do
sl^[x] := byte(round(255*x/png.Width));
end;
Basically, what I am trying to say, is that
(png.AlphaScanline[y]^)[x]
is the alpha value (the opacity), as a byte, of the pixel at row y and col x.
You could use something like this:
for Y := 0 to Image.Height - 1 do begin
Line := Image.AlphaScanline[Y];
for X := 0 to Image.Width - 1 do begin
Line[X] := ALPHA
end;
end;