3d coordinates to 2d coordinates if the point is b

Blitz3D Forums/Blitz3D Beginners Area/3d coordinates to 2d coordinates if the point is b

RemiD(Posted 2013) [#1]
Hello,

I want to draw a grid with the command Line()

With Blitz3d i know that i can use CameraProject() to find the 2D coordinates of 3D points if the points are in front of the camera.
;For point A
CameraProject(Camera,AX,AY,AZ)
APX = ProjectedX()
APY = ProjectedY()
;For point B
CameraProject(Camera,BX,BY,BZ)
BPX = ProjectedX()
BPY = ProjectedY()
;Then i draw the line
Line(APX,APY,BPX,BPY)

It works well, see :


But what if the 3d points are behind the camera ?

CameraProject() does not seem to help in this case... See :


Do you know a way to calculate the 2d coordinates (pixel coordinates) of 3d points whatever where there are in the 3d world (in front of or behind the camera) ?
If yes, can you please explain ?

Please note that my camera can have different pitch and yaw, so this complicates things even more.

Thanks,


Dale Nation(Posted 2013) [#2]
If the points are not seen by the camera, they do not have 2d coordinates... it would seem CameraProject() should work. What is the problem with it?


Stevie G(Posted 2013) [#3]
This issue is obviously that the camera projection defaults to 0,0 if the point isn't in front of the camera.

Assuming you want to draw a line between 3d points A and B, check both points to determine whether they are infront or behind the camera.

tformpoint Ax,Ay,Az,0,camera
AInfront = ( tformedz() > MIN_CAM_RANGE ) 
tformpoint Bx, By, Bz, 0, Camera
BInfront = ( tformedz() > MIN_CAM_RANGE )


I neither point is in front don't draw the line. If both points are in front then draw as normal. Else work out the intersection point between the line and a plane. The line would be from the point in front to the point behind. The plane is effectively the camera of the view fostrum. Once you have the new point, draw the line from the point infront to the intersection point.

See Line-Plane intersection on this page:
http://geomalgorithms.com/a05-_intersect-1.html

The point on the plane would be:

Tformpoint 0,0,MIN_CAM_RANGE, CAMERA, 0
Px# = tformedx()
Py# = tformedy()
Pz# = tformedz()


The plane normal would be ...

Tformnormal 0,0,1,CAMERA, 0
Nx# = tformedx()
Ny# = tformedy()
Nz# = tformedz()


MIN_CAM_RANGE will normally be 1 but this would have to be adjusted if you set a new camerrange or change the camerazoom.

Stevie


Floyd(Posted 2013) [#4]
There is also a ProjectedZ(), which will normally be the "near" value for the camera range.

But it will be zero for points not in front of the camera, or beyond the camera far value. In that case the ProjectedX() and ProjectedY() also return zero, but are meaningless.


RemiD(Posted 2013) [#5]
I want to find a way to convert the 3d coordinates of a 3d point to 2d coordinates (in pixels) even if the 3d point is behind the camera.
With this i will be able to always draw the lines of the grid even if the camera is in the middle of the grid.
With CameraProject i can't calculate the 2d coordinates of a 3d point if the 3d point is behind the camera.

This code shows that it is possible to draw a line even if the start point or end point is outside of the 2d screen


So i assumed it must be possible to do it.



Stevie G>>Thanks i will study your explanations.


Kryzon(Posted 2013) [#6]
Isn't it possible to flip the camera in it's local Z axis, do a CameraProject() with all the points that were considered behind it, then flip it again to normality and invert the signs of the XY coordinates of those backwards projections?

EDIT: No.

EDIT2: Okay, here's what you need.
Like Stevie G listed...
- For every pair of points in front of the camera, draw line like usual.
- For every pair behind the camera, don't draw anything.
- For every pair where one of the points is behind the camera, use a Ray -> Plane intersection routine by Elias_t, from the code archives: http://blitzbasic.com/codearcs/codearcs.php?code=942

- As the plane, consider the camera's NEAR PLANE (it's usually +1.0 in the Z axis of the camera). You need three points of that plane to input to the function, and you can retrieve these three points by doing TFormPoint( X, Y, 1, Camera, 0) three times, using different X and Y values. It doesn't matter what are the XY values, as long as they describe three different points that represent a triangle contained in the plane.
- As the ray, use the two 3D grid points.

The function will return the coordinates of the intersection located at your near plane, and this intersection will be 'ahead' of the camera, so CameraProject() will return valid screen coordinates that you can draw your lines.
Note this is going to cost performance, and you only need to use this function for points behind the camera - so a worst case scenario would be all your points are behind it. In any case, it's 7x faster than LinePicks.




Stevie G(Posted 2013) [#7]
@ Kryzon, that sounds like a very familiar concept!

You only need a 1 point on the plane and it's normal. You don't need to faf about with 3 points as they are primarily used to get the plane normal - you can use the camera entity to get this information. Elias's function can then be reduced to ...

Function RayPlaneIntersect(p1x#,p1y#,p1z#, p2x#,p2y#,p2z#, Entity, OffsetZ# =1.0 )

	Local d#
	Local total#,denom#,mu#
	Local nx#, ny#, nz#
	Local pax#, pay#, paz#
	
	;get a point on the plane
	TFormPoint 0,0,OffsetZ,Entity, 0
	pax# = TFormedX()
	pay# = TFormedY()
	paz# = TFormedZ()
	
	;get plane normal
	TFormNormal 0,0,1,Entity,0
	nx# = TFormedX()
	ny# = TFormedY()
	nz# = TFormedZ()

	;------------------------------------

	d = - nx * pax - ny * pay - nz * paz

	;Calculate the position on the line that intersects the plane.
	denom = nx * (p2x - p1x) + ny * (p2y - p1y) + nz * (p2z - p1z);
	
	If (Abs(denom) < 0.0001) Return False ;Line and plane don't intersect.
      
	mu = - (d + nx * p1x + ny * p1y + nz * p1z) / denom
	IntersectedX = p1x + mu * (p2x - p1x)
	IntersectedY = p1y + mu * (p2y - p1y)
	IntersectedZ = p1z + mu * (p2z - p1z)
	
	;comment this out if you want an infinite ray
	If (mu < 0 Or mu > 1) Return False ;Intersection not along line segment.
      
	Return True

End Function


Where Entity = camera entity, OffsetZ = camera near range

In fact, you could put your whole line drawing function into the same function. I haven't tested but this *should* work ..

Function Line3Dto2D( p1x#,p1y#,p1z#, p2x#,p2y#,p2z#, Entity, OffsetZ# =1.0 )

	Local d#
	Local total#,denom#,mu#
	Local nx#, ny#, nz#
	Local pax#, pay#, paz#
	Local InFront1 , InFront2
	Local x1#,y1#,x2#,y2#
	Local InterX#, InterY#, InterZ#
	
	;Is point 1 if infront of plane?
	TFormPoint p1x, p1y, p1z, 0, Entity : InFront1 = ( TFormedZ() > OffsetZ )
	;Is point 2 if infront of plane?
	TFormPoint p2x, p2y, p2z, 0, Entity : InFront2 = ( TFormedZ() > OffsetZ )
	
	;Exit if both points behind plane
	If ( Not InFront1 ) And ( Not InFront2 ) Return
	
	If InFront1 And InFront2	
		;Draw line normally
		CameraProject Entity, p1x,p1y,p1z
		x1# = ProjectedX()
		y1# = ProjectedY()
		CameraProject Entity, p2x,p2y,p2z
		x2# = ProjectedX()
		y2# = ProjectedY()
	Else
		;Get a point on the plane
		TFormPoint 0,0,OffsetZ,Entity, 0
		pax# = TFormedX()
		pay# = TFormedY()
		paz# = TFormedZ()
		
		;Get plane normal
		TFormNormal 0,0,1,Entity,0
		nx# = TFormedX()
		ny# = TFormedY()
		nz# = TFormedZ()
		
		;Calculate intersection point
		;There must be one as one Line is infront And the other is behind plane
		d = - nx * pax - ny * pay - nz * paz
		denom = nx * (p2x - p1x) + ny * (p2y - p1y) + nz * (p2z - p1z);
		mu = - (d + nx * p1x + ny * p1y + nz * p1z) / denom
		InterX = p1x + mu * (p2x - p1x)
		InterY = p1y + mu * (p2y - p1y)
		InterZ = p1z + mu * (p2z - p1z)

		If InFront1
			;Point 1 is in front, point 2 is behind
			CameraProject Entity, p1x,p1y,p1z
			x1# = ProjectedX()
			y1# = ProjectedY()
			CameraProject Entity, InterX,InterY,InterZ
			x2# = ProjectedX()
			y2# = ProjectedY()
		Else
			;Point 2 is in front, point 1 is behind
			CameraProject Entity, p2x,p2y,p2z
			x1# = ProjectedX()
			y1# = ProjectedY()
			CameraProject Entity, InterX,InterY,InterZ
			x2# = ProjectedX()
			y2# = ProjectedY()
		EndIf
	EndIf

	Line x1,y1,x2,y2

End Function



Floyd(Posted 2013) [#8]
Isn't it possible to flip the camera in it's local Z axis, do a CameraProject()...
EDIT: No.
EDIT2: Okay, here's what you need.
Like Floyd listed...

That wasn't me.

Actually my first thought was the z-flip idea. Doesn't quite work.
Then I thought of turning the camera 180 degrees on its y-axis. No.

So here's the latest version of that plan. Imagine the point, camera and plane in space. There is an infinite line determined by the point and the camera. Every point on this line projects the same point on the plane.

So if a point is behind the camera ( opposite side from the plane ) we consider the point reflected through the camera. If the camera is at the origin then just flipping all the signs would suffice. But in general we need to do a little calculation.

Let P and Q be two points in space. (P-Q) is the vector from Q to P. Then we have

P + (P-Q)

is the "mirror image" of the point Q reflected through the point P. This would be a lot clearer with picture.

Anyway, if the point is behind the camera we find the coordinates of the reflected point by

CameraX + (CameraX-PointX), and likewise for Y,Z.

Then use CameraProject on the reflected point.

This is untested, but looks right in my head.


Floyd(Posted 2013) [#9]
Hah! I gave up too soon on the "manipulate the camera" idea.

For some reason I thought the projection was on the plane z=1 even with the z-axis flipped. But that wasn't it.

So my "reflect the point through the camera" approach is most easily implemented as

ScaleEntity camera, -1,-1,-1

and then project for points behind the camera.


Kryzon(Posted 2013) [#10]
@Stevie G: Thank you for the changes. Supplying only the entity is much more convenient than having to worry about the orientation of the plane or any vertex positions. That is very interesting.

Like Floyd listed...
That wasn't me.

Disregard that, I was in auto-reply mode.
I realize now it was Stevie G; he also suggested the plane intersection, which is what inspired me to look in the code archives for some code that did just that.

About the mirror reflection, I think I understand the idea behind, but please correct me if I'm wrong. You are taking the "displacement" of the camera in relation to the point, and continuing that displacement as the point itself, so you get the equivalent of seeing this point with a flipped camera while the camera remains untouched. I was thinking of using TForm for that, but directly going to math like you are doing is much faster. The scale should do the same as well.
I still don't think it would work, because the vector from point-behind to point-in-front (which is what we wish to render on the screen as a line) is not the same as the vector from mirrored-point to point-in-front.
If you imagine a lot of scenarios for this, some of them won't fit. For instance, the point-in-front being close to the camera and the point-behind being very far away.

- - - - -
Just for trivia, Direct3D can do this task quite simply: vertex line-lists.
You define a "vertex stream" - an array of vertices - and call for DrawPrimitive on this array in line-list mode: the hardware renders 2D lines between the vertices, even if the camera is intersecting the connections between them.
It's how software like 3DS Max etc. draw their 3D grids. It would be a boon to this kind of work to have this accessible.
It would require a lot of effort to be invoked from Blitz3D, however.
You would need to download the SDK for Direct3D 7.
Then make a DLL that wraps d3dDevice->DrawPrimitive() by taking as parameters the list of vertices and the camera's matrix, setting up the immediate mode and everything.
Then create a DECLS of that.
It's not impossible, though, since you can retrieve the Direct3DDevice7 and the HWND with the SystemProperty() function.


Kryzon(Posted 2013) [#11]
Hello again. What a coincidence.
Today I got to know that the AShadow Engine, a library in the lines of FastExtention, is now freeware.

AShadow Engine wraps some of Direct3D 7. With the help of this engine, it's [somewhat] easy to perform the real 3D lines as intended without the need to calculate plane intersections or anything else.

Check the sample below:


Before running this code, you need the AShadow engine in your userlibs ( http://andreyman.ucoz.ru/load/3-1-0-5 ).




RemiD(Posted 2013) [#12]
Kryzon>>Very good ! Thanks for the example. :)


Kryzon(Posted 2013) [#13]
Hello. I've worked on my code for a bit, tried making it more robust.

Some things work differently now, and you can have multiple cameras rendering the grid, different viewports, zoom etc. I know it doesn't sound like much, but trust me in that it was complicated to achieve.
Semi-transparent entities and their interaction with the grid isn't perfect right now, but I think this can be addressed by someone taking their time with the render-states.
I was more concerned with the cameras and entity transforms, which I think are the most difficult part.

It's also got some more information for anyone trying to do more complex things with the Andreyman AShadow engine with the Direct3D immediate mode.






RemiD(Posted 2013) [#14]
Kryzon>>Wow ! This seems complicated !

Is there no other way to achieve the same render ?

I think of 2 ways :


1)Use 2 cameras, use the camera pointer to set each camera to render the grid (so 2 renders)
No idea on how to do it but i guess that :
BBBeginScene()
BBSetRenderState(7,True) ;D3DRS_ZENABLE = 7
BBSetDiffuseMaterial(255,255,255,255)
BBDrawPrimitive( 2, retI(Gridv3Surface+28), Gridv3VerticesCount, 0 )
BBEndScene()
are linked to a Camera


2)Use only one camera, render each view and copyrect to 2 different images, then draw one image above (or aside) of the other.


Anyway good work. It would be good to know if it is compatible with most machines, i will do some tests.


Kryzon(Posted 2013) [#15]
There are shortcuts you can take; If it's a game with a single camera, it's much easier to handle.
If you need more, you can code a framework behind it to take away most of the work (automating tasks like rendering all cameras, etc.).
In a way, like a RenderWorld().

Try working on this:





EDIT: Updated the code with orthogonal projection mode for the primitive rendering.


RemiD(Posted 2013) [#16]
I have not managed to implement the first approach that i mentionned but the second approach works.

Very nice render with the dots.

These renders with lines and dots could be used for a futuristic game, for example, imagine using the render with the dots to display a 3d projection like this

Or to display the result of a scan of a terrain or of an object, etc...


RemiD(Posted 2014) [#17]
Stevie G>>I want to thank you again for your Line3dTo2d code example. :)