Posted: 25th Jul 2014 2:42
Visible Camera Fustrum
Visualise the space seen by a camera.



Notes
- there are bits of vector/3d maths scattered about the code. I haven't used DBP's native vectors because this was first and foremost a learning experience.
- the UpdateFustrum function requires the cameras fov, aspect, etc. be passed in a camData UDT variable. Feel free to modify this so it plugs into a project more readily.
- the full example includes some #constants tricks to allow compiling with/without some plugins. As a result, that code is a bit messy, but the pertinent bits are in the other example anyway.
- Original motivation for this was that the d3dfunc fustrum has some issues (don't remember what they are though).

Minimum Setup
Requires Matrix1Utils.

+ Code Snippet
// structure for holding camera properties.
type t_camData
	fov as float		// camera fov.
	aspect as float		// camera aspect ratio.
	rangeNear as float	// distance from camera position to near plane
	rangeFar as float	// ...and to far plane.
	view as box2d		// description of camera viewport.
endtype



// - setup camera -


// - setup camera fustrum -
// must be a cube, ie. make object cube objFustrum, 100


// - main loop -
// * ensure camera camData is up to date
// * call UpdateFustrum with camera id, the fustrum object, and the camdata


// - functions -

function Lerp(a as float, b as float, t as float)
	a = a + (b-a) * t
endfunction a


function GetCameraRight(id)
	GetCameraDirection(id, 1, 0, 0)
endfunction


function GetCameraUp(id)
	GetCameraDirection(id, 0, 1, 0)
endfunction


function GetCameraNormal(id)
	GetCameraDirection(id, 0, 0, 1)
endfunction


function GetCameraDirection(id, bRight, bUp, bNormal)
	local option as dword, oldPos as vec3d
	
	// convert three direction choice parameters into single value, cancel if anything other than exactly one direction specified.
	option = bRight + (bUp*2) + (bNormal*4)
	if option = 0 or option = %011 or option > %100 then exitfunction
	
	oldPos.x = camera position x(id) : oldPos.y = camera position y(id) : oldPos.z = camera position z(id)
	position camera id, 0, 0, 0
	select option
		case %001 : move camera right id, 1.0 : endcase
		case %010 : move camera up id, 1.0    : endcase
		case %100 : move camera id, 1.0       : endcase
	endselect
	__vec3d.x = camera position x(id) : __vec3d.y = camera position y(id) : __vec3d.z = camera position z(id)
	position camera id, oldPos.x, oldPos.y, oldPos.z
endfunction


function UpdateFustrum(id, obj, camData as t_camData)
	local DF as float			// DF and DN are the half-heights
	local DN as float			//  of the near and far planes.
	
	local FarTL as vec3d		// calculated coords of the
	local FarTR as vec3d		//  four corners of the far plane
	local FarBR as vec3d
	local FarBL as vec3d
	
	local NearTL as vec3d		// ... and those of the near plane.
	local NearTR as vec3d
	local NearBR as vec3d
	local NearBL as vec3d
	
	// DF = tan(fov/2)*dist; DN is somewhere between 0 and DF.
	DF = tan( camData.fov * 0.5 ) * camData.rangeFar
	DN = Lerp( 0, DF, 1.0 / camData.rangeFar * camData.rangeNear )
	
	//  * the four corners of a plane:
	//     * top-left			-w#   h#
	//     * top-right			 w#   h#
	//     * bottom-right		 w#  -h#
	//     * bottom-left		-w#  -h#
	//    where w# and h# are the half-width and half-height of the plane,
	//    and in combination make the offsets of the corners from its centre.
	
	// calculation process:
	//  * get camera forward ('normal'), up, and right directional vectors.
	//  * multiply the desired offset (from the camera PoV, ie. as though the camera were at the origin and unrotated in world space)
	//    by these directional vectors.
	//  * add to the camera's position.
	
	local camPos as vec3d
	local camRight as vec3d
	local camUp as vec3d
	local camFwd as vec3d
	local camDirection as vec3d
	
	// store camera pos.
	camPos.x = camera position x(id)
	camPos.y = camera position y(id)
	camPos.z = camera position z(id)
	
	// fill camera direction vectors.
	GetCameraRight(id)  : camRight.x = __vec3d.x : camRight.y = __vec3d.y : camRight.z = __vec3d.z
	GetCameraUp(id)     : camUp.x    = __vec3d.x : camUp.y    = __vec3d.y : camUp.z    = __vec3d.z
	GetCameraNormal(id) : camFwd.x   = __vec3d.x : camFwd.y   = __vec3d.y : camFwd.z   = __vec3d.z
	
	// calculate the four corners of the far plane first.
	w# = DF * camData.aspect
	h# = DF
	
	FarTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
	FarTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
	FarTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
	
	FarTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
	FarTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
	FarTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
	
	FarBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
	FarBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
	FarBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
	
	FarBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
	FarBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
	FarBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
	
	// and now the near plane.
	w# = DN * camData.aspect
	h# = DN
	
	NearTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
	NearTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
	NearTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
	
	NearTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
	NearTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
	NearTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
	
	NearBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
	NearBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
	NearBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
	
	NearBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
	NearBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
	NearBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
	
	// finally, update the fustrum object.
	lock vertexdata for limb obj, 0
		
		// near plane
		set vertexdata position  0, NearTL.x, NearTL.y, NearTL.z
		set vertexdata position  1, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position  2, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position  3, NearBL.x, NearBL.y, NearBL.z
		
		// far plane
		set vertexdata position  7, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  6, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position  5, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position  4, FarBL.x,  FarBL.y,  FarBL.z
		
		// top face
		set vertexdata position  8, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  9, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 10, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position 11, NearTL.x, NearTL.y, NearTL.z
		
		// bottom face
		set vertexdata position 15, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 14, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 13, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 12, NearBL.x, NearBL.y, NearBL.z
		
		// right face
		set vertexdata position 16, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 17, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 18, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 19, NearTR.x, NearTR.y, NearTR.z
		
		// left face
		set vertexdata position 23, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position 22, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 21, NearBL.x, NearBL.y, NearBL.z
		set vertexdata position 20, NearTL.x, NearTL.y, NearTL.z
		
	unlock vertexdata
	
	// recalc normals (lighting) and bounds (occlusion) so obj displays correctly.
	set object normals obj
	calculate object bounds obj
	
endfunction


Simple Example
Requires Matrix1Utils.

+ Code Snippet
// @todo: remove dependance on M1U (move camera right, etc.) for simple example
// basic UDTs.
// 3d vector.
type vec3d	 x as float   y as float   z as float   endtype

// 2d box (tl~br corners, plus optional width and height cache).
type box2d   x1 as float  y1 as float  x2 as float y2 as float
             w as float   h as float   endtype

// special vars for returning UDTs from function.
global __vec3d as vec3d
global __box2d as box2d

// world vectors.
global worldRight as vec3d, worldUp as vec3d, worldFwd as vec3d
worldRight.x = 1.0		: worldRight.y = 0		: worldRight.z = 0
worldUp.x = 0			: worldUp.y = 1.0		: worldUp.z = 0
worldFwd.x = 0			: worldFwd.y = 0		: worldFwd.z = 1.0

// setup display.
global scrW, scrH, scrAspect as float
scrW = desktop width()
scrH = desktop height()
scrAspect = scrW*1.0 / scrH

set display mode scrW, scrH, 32, 1

// first sync always shows blank, so do it now to prime sync buffer.
sync on
sync rate 30
sync


// -- camera setup --
// we will have two cameras:
// mainCam (0) - full screen
// testCam (1) - inset, adjustable

// structure for holding camera properties.
// (we will be setting position/rotation directly, so they're not needed here).
type t_camData
	fov as float		// camera fov.
	aspect as float		// camera aspect ratio.
	rangeNear as float	// distance from camera position to near plane
	rangeFar as float	// ...and to far plane.
	view as box2d		// description of camera viewport.
endtype

dim cam(1) as t_camData

// setup mainCam.
global mainCam = 0
color backdrop mainCam, 0xff5d7deb
SetCamData(mainCam, 61.9621391296, scrAspect, 0.1, 16383.0, 0, 0, scrW, scrH)
UpdateCam(mainCam, 1, 1, 1, 1)
position camera mainCam, 0, 10, -50
point camera mainCam, 0, 0, 0

// setup testCam.
// view is at BL corner, 10px from screen edge, 300px tall and 480px wide (for aspect ratio of 1.6).
global testCam = 1
make camera testCam
color backdrop testCam, 0xffa0b0d0
SetCamData(testCam, 61.9621391296, 1.6, 10.0, 384.0, 10, scrH-310, 490, scrH-10)
UpdateCam(testCam, 1, 1, 1, 1)

set current camera 0


// -- scene visuals --

// - 3d position marker -
// used to get 2d screen coords from 3d position for 3d drawing.
// will be hidden so geom doesn't matter, but a triangle is fine.
global objMarker
objMarker = 1
make object triangle objMarker, 0, 1, 0, 0, 0, 1, 1, 0, 0
hide object objMarker

// - fustrum object for testCam -
// this is a cube that will be reshaped on the fly to match the camera fustrum.
objFustrum = 2
make object cube objFustrum, 100		// size is irrelevant but must be set anyhow.
set object mask objFustrum, %01			// only show it in mainCam.
set object cull objFustrum, 0			// (these lines create a seethrough effect.)
set object transparency objFustrum, 6	// better drawing on stuff inside fustrum.
set alpha mapping on objFustrum, 40		// make it seethroughable.

// - scenery - sphere -
objTeapot = 3
make object sphere objTeapot, 50					
position object objTeapot, 0, 0, 512
color object objTeapot, 0xffffffff					// color white so it responds to light.
set object ambient objTeapot, 0						// remove ambient for punchier lighting.
set object cull objTeapot, 0						// deemphasises gaps in model & lets you see inside.

// scenery - spherical field of cubes -
baseObj = 4
make object cube baseObj, 25
hide object baseObj

for n = 1 to 800
	obj = n+4
	instance object obj, baseObj
	rotate object obj, rnd(360), rnd(360), rnd(360)
	move object obj, 600 + rnd(1000)
next n


// -- input/interaction --

// scancodes for WASD keys: kbW = KBSCAN_W keystate, etc.
#constant KBSCAN_W          17
#constant KBSCAN_A          30
#constant KBSCAN_S          31
#constant KBSCAN_D          32

// input variables.
global mmx as float, mmy as float				// mouse move amount.
global kbW, kbA, kbS, kbD						// WASD keystates.

// flush out any captured mouse movement prior to start.
mmx = mousemovex()
mmy = mousemovey()
mmz = mousemovez()

// main camera interaction variables.
camRotSpeedX as float							// these values used for
camRotSpeedY as float							//  smooth camera rotation.


// -- ## main loop ## -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

DO
	// - get input -
	mmx = mousemovex()
	mmy = mousemovey()
	
	kbW = keystate(KBSCAN_W)
	kbA = keystate(KBSCAN_A)
	kbS = keystate(KBSCAN_S)
	kbD = keystate(KBSCAN_D)
	
	
	// - update camera -
	
	i$ = lower$(inkey$())
	if i$ <> ""
		select i$
			case "z" : cam(testCam).fov = 30 + rnd(120)				: UpdateCam(testCam, 1, 0, 0, 0) : wait 100 : endcase
			case "x" : cam(testCam).aspect = 0.4 + 0.1 * rnd(12)	: UpdateCam(testCam, 0, 1, 0, 0) : wait 100 : endcase
			case "c" : cam(testCam).rangeNear = 10 + rnd(100)		: UpdateCam(testCam, 0, 0, 1, 0) : wait 100 : endcase
			case "v" : cam(testCam).rangeFar = 110 + rnd(3000)		: UpdateCam(testCam, 0, 0, 1, 0) : wait 100 : endcase
		endselect
	endif

	// slow rotation and movement speed for high-fov testCam.
	// scale factor: min( 1.0, 0.0056 + (0.0111 * [1..179]) )
	//          -->  min( 1.0, 0.0056 + [0.0111 .. 1.9889]  )
	//          -->  min( 1.0, [0.0167 .. 1.9945]           )
	//          -->  [0.0167 .. 1.0].
	// hits 1.0x limit at 90 degrees, gets progressively slower towards 1 degree
	sf# = 0.0056 + (0.0111 * cam(testCam).fov)
	if sf# < 1.0 then sf# = 1.0
		
	// smooth rotate camera.
	camRotSpeedX = Lerp(camRotSpeedX, mmy * 0.6 * sf#, 0.4)
	camRotSpeedY = Lerp(camRotSpeedY, mmx * 0.6 * sf#, 0.4)
	
	rotate camera testCam, camera angle x(testCam) + camRotSpeedX, camera angle y(testCam) + camRotSpeedY, 0
	
	// WASD navigation.
	move camera testCam, ( kbW - kbS ) * 10.0 * sf#
	move camera right testCam, ( kbD - kbA ) * 10.0 * sf#
	
	
	// - render -
	// note that drawing is designed to handle working both with and without d3dfunc available.
	
	// - update testCam's visible fustrum -
	UpdateFustrum(testCam, objFustrum)
	
	// testcam info: dimensions, position, orientation.
	
	text cam(testCam).view.x1+1, cam(testCam).view.y1-17, str$(cam(testCam).view.w) + "x" + str$(cam(testCam).view.h)
	center text cam(testCam).view.x1+cam(testCam).view.w/2, cam(testCam).view.y2-15, "pos: " + str$(camera position x(testCam),1) + ", " + str$(camera position y(testCam),1) + ", " + str$(camera position z(testCam),1) + "    rot: " + ", " + str$(camera angle x(testCam),1) + ", " + str$(camera angle y(testCam),1) + ", " + str$(camera angle z(testCam),1)
	
	// prompt
	prompt$ = "Mouse + WASD to navigate. ZXCV randomise camera settings."
	text scrW - text width(prompt$), 0, prompt$
	text 0, 0, "[z] fov:    " + str$(cam(testCam).fov, 1)
	text 0, 15, "[x] aspect: " + str$(cam(testCam).aspect, 4)
	text 0, 30, "[c] near:   " + str$(cam(testCam).rangeNear, 4)
	text 0, 45, "[v] far:    " + str$(cam(testCam).rangeFar, 4)
	
	sync
	
	// testCam position eye marker
	GetCameraNormal(testCam)
	cx# = camera position x(testCam) : cy# = camera position y(testCam) : cz# = camera position z(testCam)
	vx# = __vec3d.x : vy# = __vec3d.y : vz# = __vec3d.z
	Draw_Circle3d(mainCam, 10.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xff080808, 1)
	Draw_Circle3d(mainCam,  8.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xff080808, 1)
	Draw_Circle3d(mainCam,  9.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xfff7f7f7, 1)
	Draw_AAPoint3d(mainCam, 1.0, cx#, cy#, cz#, 0xff080808, 0xff080808, 0xff080808, 0)
	Draw_Line3d(mainCam, cx#, cy#, cz#, cx#+vx#*cam(testCam).rangeNear, cy#+vy#*cam(testCam).rangeNear, cz#+vz#*cam(testCam).rangeNear, 0, 0xff080808, 0, 1)
	
	// extra circle on marker indicating testCam's top side
	GetCameraUp(testCam)
	ux# = __vec3d.x : uy# = __vec3d.y : uz# = __vec3d.z
	Draw_Line3d(mainCam, cx#, cy#, cz#, cx#+ux#*8.0, cy#+uy#*8.0, cz#+uz#*8.0, 0, 0xff080808, 0, 1)
	Draw_Circle3d(mainCam, 1.0, cx#+ux#*9.0, cy#+uy#*9.0, cz#+uz#*9.0, vx#, vy#, vz#, 0xff080808, 1)
	
LOOP



// -- function block  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --


// Linear intERPolate.
function Lerp(a as float, b as float, t as float)
	a = a + (b-a) * t
endfunction a


// returns either -1 or 1, at random:
//  out = 1 - [0,1] * 2
//      = 1 - [0,2]
//      = [1,-1]
function RndSgn()
	out = 1 - rnd(1)*2
endfunction out


// calculates the cross product (perpendicular vector) of two vectors.
function CrossProduct(a as vec3d, b as vec3d)
	__vec3d.x = a.y * b.z - a.z * b.y
	__vec3d.y = a.z * b.x - a.x * b.z
	__vec3d.z = a.x * b.y - a.y * b.x
endfunction


// normalises a vector (divides its components by its length, to make it unit (=1) length).
function Normalise(in as vec3d)
	length# = sqrt(in.x*in.x + in.y*in.y + in.z*in.z)
	__vec3d.x = in.x / length#
	__vec3d.y = in.y / length#
	__vec3d.z = in.z / length#
endfunction


// prepares cam data for specified camera.
function SetCamData(id, fov#, asp#, near#, far#, vx1#, vy1#, vx2#, vy2#)
	local out as t_camData
	
	out.fov       = fov#
	out.aspect    = asp#
	out.rangeNear = near#
	out.rangeFar  = far#
	out.view.x1   = vx1#
	out.view.y1   = vy1#
	out.view.x2   = vx2#
	out.view.y2   = vy2#
	out.view.w    = vx2# - vx1#
	out.view.h    = vy2# - vy1#
	
	cam(id) = out
endfunction


// updates physical camera from its cam settings.
function UpdateCam(id, bFov, bAspect, bRange, bView)
	local camData as t_camData
	camData = cam(id)
	
	if bFov    then set camera fov    id, camData.fov
	if bAspect then set camera aspect id, camData.aspect
	if bRange  then set camera range  id, camData.rangeNear, camData.rangeFar
	if bView   then set camera view   id, camData.view.x1, camData.view.y1, camData.view.x2, camData.view.y2
endfunction


function GetCameraRight(id)
	GetCameraDirection(id, 1, 0, 0)
endfunction

function GetCameraUp(id)
	GetCameraDirection(id, 0, 1, 0)
endfunction

function GetCameraNormal(id)
	GetCameraDirection(id, 0, 0, 1)
endfunction

function GetCameraDirection(id, bRight, bUp, bNormal)
	local option as dword, oldPos as vec3d
	
	// convert three direction choice parameters into single value, cancel if anything other than exactly one direction specified.
	option = bRight + (bUp*2) + (bNormal*4)
	if option = 0 or option = %011 or option > %100 then exitfunction
	
	oldPos.x = camera position x(id) : oldPos.y = camera position y(id) : oldPos.z = camera position z(id)
	position camera id, 0, 0, 0
	select option
		case %001 : move camera right id, 1.0 : endcase
		case %010 : move camera up id, 1.0    : endcase
		case %100 : move camera id, 1.0       : endcase
	endselect
	__vec3d.x = camera position x(id) : __vec3d.y = camera position y(id) : __vec3d.z = camera position z(id)
	position camera id, oldPos.x, oldPos.y, oldPos.z
endfunction


function UpdateFustrum(id, obj)
	local camData as t_camData
	local DF as float			// DF and DN are the half-heights
	local DN as float			//  of the near and far planes.
	
	local FarTL as vec3d		// calculated coords of the
	local FarTR as vec3d		//  four corners of the far plane
	local FarBR as vec3d
	local FarBL as vec3d
	
	local NearTL as vec3d		// ... and those of the near plane.
	local NearTR as vec3d
	local NearBR as vec3d
	local NearBL as vec3d
	
	camData = cam(id)
	
	// DF = tan(fov/2)*dist; DN is somewhere between 0 and DF.
	DF = tan( camData.fov * 0.5 ) * camData.rangeFar
	DN = Lerp( 0, DF, 1.0 / camData.rangeFar * camData.rangeNear )
	
	//  * the four corners of a plane:
	//     * top-left			-w#   h#
	//     * top-right			 w#   h#
	//     * bottom-right		 w#  -h#
	//     * bottom-left		-w#  -h#
	//    where w# and h# are the half-width and half-height of the plane,
	//    and in combination make the offsets of the corners from its centre.
	
	// - USING DBP -
	// calculation process:
	//  * get camera forward ('normal'), up, and right directional vectors.
	//  * multiply the desired offset (from the camera PoV, ie. as though the camera were at the origin and unrotated in world space)
	//    by these directional vectors.
	//  * add to the camera's position.
	// An example (finding same point for same camera as in ezrotate example above):
	//  * the camera is at position 0,0,10, orientation 90,0,0
	//  * GetCameraX calls give us:
	//     * right vector: 1, 0, 0 (camera 'right' (+x) coincides with world space 'right' (+x); this makes sense - cam looked down, but didn't turn to left or right)
	//     * up vector:    0, 0, 1 (that is, 'up' (+y) for the camera is 'forwards' (+z) in world space; if you look down, the top of your head points forwards)
	//     * fwd vector:   0,-1, 0 (that is, the camera is looking down, so forwards from it's PoV is downwards in world space)
	//    in summary, cam:forward -> world:down, cam:up -> world:forward, and cam:right -> world:right.
	//  * we want the point 0,0,5 relative to the camera
	//    that is, the point is at (0)   (1, 0, 0)   (0*1  0*0   0*0)   (0, 0, 0)
	//                             (0) * (0, 0, 1) = (0*0  0*0   0*1) = (0, 0, 0) 
	//                             (5)   (0,-1, 0)   (5*0  5*-1  5*0)   (0,-5, 0)
	//                                                                  ---------
	//                                                                  (0,-5, 0)
	//    offset in column vector times 3x3 matrix where top row is
	//    right vector (1,0,0), middle row is up (0,0,1), and bottom row forward (0,-1,0).
	//    the resulting vector has x equal to the sum of the first matrix column,
	//    y equal to sum of the second, and z equal to sum of the third - in this
	//    case:
	//     * x = 0+0+0 = 0      * y = 0+0+(-5) = -5      * z = 0+0+0 = 0
	//  * finally, remember to add the camera coords:
	//     * x = 0 + 0 = 0      * y = -5 + 0 = -5        * z = 0 + 10 = 10
	//  * SO, the point is at 0,-5,10 in world space.
	//    in summary, position = camPos + offset * camDirection
	
	local camPos as vec3d
	local camRight as vec3d
	local camUp as vec3d
	local camFwd as vec3d
	local camDirection as vec3d
	
	// store camera pos.
	camPos.x = camera position x(id)
	camPos.y = camera position y(id)
	camPos.z = camera position z(id)
	
	// fill camera direction vectors.
	GetCameraRight(id)  : camRight.x = __vec3d.x : camRight.y = __vec3d.y : camRight.z = __vec3d.z
	GetCameraUp(id)     : camUp.x    = __vec3d.x : camUp.y    = __vec3d.y : camUp.z    = __vec3d.z
	GetCameraNormal(id) : camFwd.x   = __vec3d.x : camFwd.y   = __vec3d.y : camFwd.z   = __vec3d.z
	
	// calculate the four corners of the far plane first.
	w# = DF * camData.aspect
	h# = DF
	
	FarTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
	FarTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
	FarTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
	
	FarTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
	FarTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
	FarTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
	
	FarBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
	FarBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
	FarBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
	
	FarBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
	FarBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
	FarBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
	
	// and now the near plane.
	w# = DN * camData.aspect
	h# = DN
	
	NearTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
	NearTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
	NearTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
	
	NearTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
	NearTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
	NearTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
	
	NearBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
	NearBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
	NearBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
	
	NearBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
	NearBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
	NearBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
	
	// finally, update the fustrum object.
	lock vertexdata for limb obj, 0
		
		// near plane
		set vertexdata position  0, NearTL.x, NearTL.y, NearTL.z
		set vertexdata position  1, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position  2, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position  3, NearBL.x, NearBL.y, NearBL.z
		
		// far plane
		set vertexdata position  7, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  6, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position  5, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position  4, FarBL.x,  FarBL.y,  FarBL.z
		
		// top face
		set vertexdata position  8, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  9, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 10, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position 11, NearTL.x, NearTL.y, NearTL.z
		
		// bottom face
		set vertexdata position 15, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 14, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 13, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 12, NearBL.x, NearBL.y, NearBL.z
		
		// right face
		set vertexdata position 16, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 17, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 18, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 19, NearTR.x, NearTR.y, NearTR.z
		
		// left face
		set vertexdata position 23, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position 22, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 21, NearBL.x, NearBL.y, NearBL.z
		set vertexdata position 20, NearTL.x, NearTL.y, NearTL.z
		
	unlock vertexdata
	
	// recalc normals (lighting) and bounds (occlusion) so obj displays correctly.
	set object normals obj
	calculate object bounds obj
	
endfunction



function Draw_AAPoint3d(camId, size#, x#, y#, z#, colorx as dword, colory as dword, colorz as dword, zEnable)
	Draw_Line3D(camId, x#-size#, y#, z#, x#+size#, y#, z#, 1, colorx, colorx, zenable)
	Draw_Line3D(camId, x#, y#-size#, z#, x#, y#+size#, z#, 1, colory, colory, zenable)
	Draw_Line3D(camId, x#, y#, z#-size#, x#, y#, z#+size#, 1, colorz, colorz, zenable)
endfunction

function Draw_Line3d(camId, x1#, y1#, z1#, x2#, y2#, z2#, bSetAllColors, c1 as dword, c2 as dword, zEnable)
	local x1, y1, x2, y2
	position object objMarker, x1#, y1#, z1#
	if object in screen(objMarker)
		x1 = object screen x(objMarker) : y1 = object screen y(objMarker)
		position object objMarker, x2#, y2#, z2#
		if object in screen(objMarker)
			x2 = object screen x(objMarker) : y2 = object screen y(objMarker)
			ink c1, 0
			line x1, y1, x2, y2
			ink 0xffffffff, 0
		endif
	endif
endfunction

function Draw_Circle3d(camId, r#, x#, y#, z#, nx#, ny#, nz#, color as dword, zEnable)
	local camDistSq as float, rSq as float, aStep, x1, y1, x2, y2, rVec as vec3d, uVec as vec3d, fVec as vec3d
	
	// Up Vector = (nx#,ny#,nz#) from input parameters.
	// Fwd Vector = Up Vector x World Up (0,1,0).
	// Right Vector = Up Vector x Fwd Vector.
	
	normD# = sqrt(nx#*nx# + ny#*ny# + nz#*nz#)
	if normD# = 0 then exitfunction
	
	uVec.x = nx# / normD#
	uVec.y = ny# / normD#
	uVec.z = nz# / normD#
	
	CrossProduct(uVec, worldUp)
	Normalise(__vec3d)
	fVec = __vec3d
	
	CrossProduct(uVec, fVec)
	Normalise(__vec3d)
	rVec = __vec3d
	
	// change circle quality with camera distance.
	rSq = r#*r#
	camDistSq = (x# - camera position x(camId))^2 + (y# - camera position y(camId))^2 + (z# - camera position z(camId))^2 - rSq
	if camDistSq > rSq*14400.0 : aStep = 90 : else : if camDistSq > rSq*576.0 : aStep = 60 : else : aStep = 24 : endif : endif
	
	ink color, 0
	
	for a = 0 to 360 step aStep
		// position around circle, sample screen position, draw lines
		RcosA# = cos(a) * r# : RsinA# = sin(a) * r#
		_x# = x# + rVec.x * RcosA# + fVec.x * RsinA#
		_y# = y# + rVec.y * RcosA# + fVec.y * RsinA#
		_z# = z# + rVec.z * RcosA# + fVec.z * RsinA#
		position object objMarker, _x#, _y#, _z#
		x2 = object screen x(objMarker) : y2 = object screen y(objMarker)
		if object in screen(objMarker) and a <> 0 then line x1, y1, x2, y2`, color
		x1 = x2 : y1 = y2
	next a
	ink 0xffffffff, 0
endfunction



(Full example in next post)
Posted: 25th Jul 2014 2:46
(oops - the 'post too long' page put an extra [DBP] in the thread subject, didn't catch it)

Full Example.
Requires Matrix1Utils.
Optionally, can use ezRotate and d3dFunc, see #constants at start.

+ Code Snippet
// -- initialisation --

// Do you have EZrotate (either full or free versions)?
// [uncomment these if using EZrotate]
`#constant EZRO_BLOCKSTART remstart remend
`#constant EZRO_BLOCKEND remstart remend
`#constant NOEZRO_BLOCKSTART remstart `remend
`#constant NOEZRO_BLOCKEND remend
// [uncomment these if not using EZrotate]
#constant EZRO_BLOCKSTART remstart `remend
#constant EZRO_BLOCKEND remend
#constant NOEZRO_BLOCKSTART remstart remend
#constant NOEZRO_BLOCKEND remstart remend

// Do you have D3DFunc (free)?
// [uncomment these if using d3dFunc]
#constant D3DFUNC_BLOCKSTART remstart remend 
#constant D3DFUNC_BLOCKEND remstart remend 
#constant NOD3DFUNC_BLOCKSTART remstart `remend
#constant NOD3DFUNC_BLOCKEND remend 
// [uncomment these if not using d3dFunc]
`#constant D3DFUNC_BLOCKSTART remstart `remend
`#constant D3DFUNC_BLOCKEND remend
`#constant NOD3DFUNC_BLOCKSTART remstart remend
`#constant NOD3DFUNC_BLOCKEND remstart remend



// basic UDTs.
// 3d vector.
type vec3d	 x as float   y as float   z as float   endtype

// 2d box (tl~br corners, plus optional width and height cache).
type box2d   x1 as float  y1 as float  x2 as float y2 as float
             w as float   h as float   endtype

// special vars for returning UDTs from function.
global __vec3d as vec3d
global __box2d as box2d

// world vectors. ('i,j,k' yeah?)
global worldRight as vec3d, worldUp as vec3d, worldFwd as vec3d
worldRight.x = 1.0		: worldRight.y = 0		: worldRight.z = 0
worldUp.x = 0			: worldUp.y = 1.0		: worldUp.z = 0
worldFwd.x = 0			: worldFwd.y = 0		: worldFwd.z = 1.0

// setup display.
// aspect ratio of any window/viewport/etc. is its width over its height.
global deskW, deskH
deskW = desktop width()
deskH = desktop height()

global scrW, scrH, scrAspect as float
scrW = deskW
scrH = deskH
scrAspect = scrW*1.0 / scrH

set display mode scrW, scrH, 32, 1

// first sync always shows blank, so do it now to prime sync buffer.
sync on
sync rate 30
sync


// d3d requires re-init'ing after display mode changes.
D3DFUNC_BLOCKSTART
	d3d_init
D3DFUNC_BLOCKEND

// - misc resources -
// setup text.
set text font "courier new"

// red and blue textures for depth precision test planes.
imgRed  = 1 : cls 0xffff0000 : get image imgRed,  0, 0, 2, 2
imgBlue = 2 : cls 0xff0000ff : get image imgBlue, 0, 0, 2, 2

// small "auto" text for the auto aspect button.
set text size 8
imgAuto = 3 : cls 0xff000000 : text 0, 0, "auto"
get image imgAuto, 0, 0, text width("auto"), 8, 1
set text size 15	// reset text size.

// text for the parallax billboard.
imgPB = 4
cls 0xff000000 : text 0, 0, "An orthographic view" + crlf$() + "can be approximated" + crlf$() + "with a low FOV and" + crlf$() + "great distance."
get image imgPB, 0, 0, text width("An orthographic view"), 60, 1

cls 0xff000000

// -- camera setup --
// note: 61.9621391296 is dbp default fov.

// we will have two cameras:
// mainCam (0) - full screen, exists by default, orbiting/zooming camera
// testCam (1) - inset, adjustable from ui, first-person camera

// structure for holding camera properties.
// (we will be setting position/rotation directly, so they're not needed here).
type t_camData
	fov as float		// camera fov.
	aspect as float		// camera aspect ratio.
	rangeNear as float	// distance from camera position to near plane...
	rangeFar as float	// ...and to far plane.
	view as box2d		// description of camera viewport.
endtype

dim cam(1) as t_camData

// setup mainCam.
global mainCam = 0
color backdrop mainCam, 0xff5d7deb
SetCamData(mainCam, 61.9621391296, scrAspect, 0.1, 16383.0, 0, 0, scrW, scrH)
UpdateCam(mainCam, 1, 1, 1, 1)

// setup testCam.
// view is at BL corner, 10px from screen edge, initially 300px tall and 480px wide (for aspect ratio of 1.6).
global testCam = 1
make camera testCam
color backdrop testCam, 0xffa0b0d0
SetCamData(testCam, 61.9621391296, 1.6, 10.0, 384.0, 10, scrH-310, 490, scrH-10)
UpdateCam(testCam, 1, 1, 1, 1)

// we'll be drawing some debug stuff onto the camera views. Because testCam's
// view overlaps mainCam, d3d_xxx3d draws for mainCam will also appear over
// testCam. To get around this, testCam's output will be directed to an image
// which will be pasted after mainCam's d3d stuff, thus appearing on top.
// (note that when resizing the camera view, we will have to delete and remake the
// camera for the image to behave correctly.)
imgTestCamRender = 5
set camera to image testCam, imgTestCamRender, cam(testCam).view.w, cam(testCam).view.h

set current camera 0


// -- scene visuals --

// required for improved visuals
set normalization on

// - 3d position marker -
// used if d3d not available to get 2d screen coords from 3d position for 3d drawing.
// will be hidden so geom doesn't matter, but a triangle is fine.
global objMarker
objMarker = find free object()
make object triangle objMarker, 0, 1, 0, 0, 0, 1, 1, 0, 0
hide object objMarker

// - fustrum object for testCam -
// this is a cube that will be reshaped on the fly to match the camera fustrum.
objFustrum = find free object()
make object cube objFustrum, 100		// size is irrelevant but must be set anyhow.
set object mask objFustrum, %01			// only show it in mainCam.
set object cull objFustrum, 0			// (these lines create a seethrough effect.)
set object transparency objFustrum, 6	// better drawing on stuff inside fustrum.
set alpha mapping on objFustrum, 40		// make it seethroughable.
fade object objFustrum, 120				// brighten it a touch.

// - scenery - utah teapot -
// this is just something to look at and to demonstrate effects of cam settings.
objTeapot = find free object()
D3DFUNC_BLOCKSTART
	d3d_make_teapot objTeapot						// milk and sugar in v1.1
D3DFUNC_BLOCKEND
NOD3DFUNC_BLOCKSTART
	make object sphere objTeapot, 1					
NOD3DFUNC_BLOCKEND
scale object objTeapot, 5000, 5000, 5000			// (misc setup stuff)
position object objTeapot, 0, 0, 512
color object objTeapot, 0xffffffff					// color white so it responds to light.
set object ambient objTeapot, 0						// remove ambient for punchier lighting.
set object cull objTeapot, 0						// deemphasises gaps in model & lets you see inside.

// scenery - cam depth precision test planes -
// pairs of planes. pairs nearer to origin are closer together.
// as you change testCam near/far range settings, you can see how it affects
// precision. as near values approach 0, z-fighting become increasingly apparent.
for pair = 0 to 40
	for z = 0 to 1
		obj = find free object()
		make object plane obj, 20, 100
		yrotate object obj, 90
		position object obj, 150.0 + 0.02 * z * pair, 0, pair * 20.0
		texture object obj, z+1		// z=0, z+1=1 = imgRed; z=1, z+1=2 = imgBlue.
		set object cull obj, 0
		set object light obj, 0		// just want flat coloured planes, no shading.
	next z
next pair

// scenery - a field to explore (don't mind the technique and fudged numbers here, it's not important) -
baseObj = find free object()
make object box baseObj, 160, 4, 128
hide object baseObj

for x = 0 to 640 step 10
	for y = 0 to 640 step 10
		for z = 0 to 640 step 10
			if rnd(1900 + cos(z*8)*1998 + max(0, sin(x*3)*200) + y^1.4) = 0
				oobj = obj
				obj = find free object()
				instance object obj, baseObj
				position object obj, -3000 + x*3.2 * cos(y*1.3), -768 + y*3.2 + 0.01*rnd(5000), -2048 + z*3.2 * sin(y*0.5) - y^1.4
				scale object obj, 100 + y*0.1, 100, 100 + (y*0.1)^1.4
				if oobj <> 0 then point object obj, object position x(oobj), object position y(oobj), object position z(oobj)
			endif
		next z
	next y
next x

// scenery - a parallax billboard which can be read when the test camera is placed and set right -
// 1. start with a plane textured with a text image. The plane is composed of 20x10 sub-plane limbs, each a grid of 2x2 squares.
objPB = find free object()
w = image width(imgPB) : h = image height(imgPB)
make object plane objPB, w, h, 2, 2, 20, 10
texture object objPB, imgPB
// 2. iterate through limbs and for each, calculate a random displacement and shift all its vertices by that (plus a little extra fine noise).
//    also set UVs to spread texture over the whole plane (UVs are in 0..1 range).
lMax = get limb count(objPB)
for l = 0 to lMax
	xoff# = limb offset x(objPB, l)
	zoff# = limb offset z(objPB, l)
	lock vertexdata for limb objPB, l
		disp# = 250.0 * (0.01 * rnd(100))^2
		vc = get vertexdata vertex count() - 1
		for vi = 0 to vc
			x# = get vertexdata position x(vi)
			y# = get vertexdata position y(vi) - disp# - 0.1*rnd(5)
			z# = get vertexdata position z(vi)
			set vertexdata position vi, x#, y#, z#
			set vertexdata uv vi, -0.5 + 1.0/w * (xoff# + x#), 0.5 - 1.0/h * (zoff# + z#)
		next vi
	unlock vertexdata
next l
// 3. lastly, setup object in space as normal with some slight random rotation so you have to search a little for the viewing sweet spot.
position object objPB, -800, 0, 800
rotate object objPB, -85-rnd(10), -5+rnd(10), 0
set object cull objPB, 0
calculate object bounds objPB

// scenery - some lights -
// soft directional lighting for -x, +x, -y, +y, -z, +z directions. Most easily observed on the teapot.
make light 1 : set directional light 1, -1.0,  0.0,  0.0 : color light 1, 0xff200000
make light 2 : set directional light 2,  1.0,  0.0,  0.0 : color light 2, 0xff002020
make light 3 : set directional light 3,  0.0, -1.0,  0.0 : color light 3, 0xff002000
make light 4 : set directional light 4,  0.0,  1.0,  0.0 : color light 4, 0xff200020
make light 5 : set directional light 5,  0.0,  0.0, -1.0 : color light 5, 0xff000020
make light 6 : set directional light 6,  0.0,  0.0,  1.0 : color light 6, 0xff202000


// -- input/interaction --

// mouse click state constants. typically, primary = left, secondary = right.
#constant MCLICK_NONE       %000
#constant MCLICK_PRIMARY    %001
#constant MCLICK_SECONDARY  %010
#constant MCLICK_WHEEL      %100

// scancodes for WASD keys: kbW = KBSCAN_W keystate, etc.
#constant KBSCAN_W          17
#constant KBSCAN_A          30
#constant KBSCAN_S          31
#constant KBSCAN_D          32

// scancode for Tab key (used for toggling ui) and C & V keys.
#constant KBSCAN_TAB        15
#constant KBSCAN_C          46
#constant KBSCAN_V          47

// control panel control identifiers.
#constant CON_NONE          0
#constant CON_FOV           1					`  fov slider
#constant CON_ASPECT        2					`  aspect ratio slider
#constant CON_NEAR          3					`  near range slider
#constant CON_FAR           4					`  far range slider
#constant CON_AUTOASPECT    5					`  automatic aspect ratio toggle
#constant CON_CAMHEIGHT     6					`  testCam height splitter
#constant CON_CAMWIDTH      7					`  testCam width splitter
#constant CON_ZOOM          8					`  mainCam zoom level slider

// input variables.
global mx, my									// mouse position.
global cmx, cmy									// mouse position at time of click.
global omc, mc									// old and current click state.
global mmx as float, mmy as float, mmz as float	// mouse move amount.
global kbW, kbA, kbS, kbD						// WASD keystates.
global okbTab, kbTab							// Tab old and current keystate.
global okbCtrl, kbCtrl, okbC, kbC, okbV, kbV	// Ctrl, C and V keys old and current keystates.
global bMouseVisible as boolean = 1				// flag indicating mouse visibility state.

// flush out any captured mouse movement prior to start.
mmx = mousemovex()
mmy = mousemovey()
mmz = mousemovez()

// main camera interaction variables.
activeCam = -1									// camera user is interacting with.
mainCamOrbitDist       as float = 512.0			// current camera distance from its orbit centre.
targetMainCamOrbitDist as float = 512.0			// the desired distance (lerp'd to over time).
camRotSpeedX as float							// these values used for
camRotSpeedY as float							//  smooth camera rotation.

// control panel variables.
global panelEnabled as boolean = 1				// panel show/hide toggle state.
activeCon = -1									// control user is interacting with.
global panel as box2d							// area occupied by control panel.
global panelLeftPad, panelRightPad				// inner padding amount for controls.
autoAspect as boolean							// toggle: set testCam AR automatically?
minFov    as float = 0.1						// (min and max values for testCam properties.)
maxFov    as float = 179.0
minAspect as float = 0.5
maxAspect as float = 5.0
minRange  as float = 0.0001						// (these apply to both near
maxRange  as float = 65535.0					//   and far ranges.)

// setup panel area box.
UpdateControlPanel()

panelLeftPad  = 128								// (these just give room around the panel controls
panelRightPad = 32								//   for the labels and auto-aspect button.)

// pre-push mainCam into its orbit. Each loop we handle navigation by transporting it to its orbit centre, applying
// movement and rotation, and then pushing it back out. Because we move it forward at the start of the loop, we
// should ensure the camera is in the state it would be after the end of the loop - in orbit.
move camera mainCam, -mainCamOrbitDist

// -- ## main loop ## -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

DO
	// - get input -
	omc = mc
	mx  = mousex()
	my  = mousey()
	mmx = mousemovex()
	mmy = mousemovey()
	mmz = mousemovez()
	
	if mc = 0
		cmx = mx
		cmy = my
	endif
	
	mc  = mouseclick()
	
	kbW = keystate(KBSCAN_W)
	kbA = keystate(KBSCAN_A)
	kbS = keystate(KBSCAN_S)
	kbD = keystate(KBSCAN_D)
	
	okbTab = kbTab
	kbTab = keystate(KBSCAN_TAB)
	
	okbCtrl = kbCtrl
	okbC = kbC
	okbV = kbV
	kbCtrl = controlkey()
	kbC = keystate(KBSCAN_C)
	kbV = keystate(KBSCAN_V)
	
	
	// - update cameras and ui -
	
	// mainCam's orbiting works as follows:
	//  * move camera to its orbit centre (move forward by orbit dist) (we do this now)
	//  * apply position/rotation updates (and update orbit dist if user zooms)
	//  * push camera back into orbit (move backward by new orbit dist)
	move camera mainCam, mainCamOrbitDist
	
	// ui toggling with tab key.
	if okbTab = 0 and kbTab then panelEnabled = 1 - panelEnabled
	
	// check what ui control or camera is under mouse (we do this outside of if block as we rely on its value while drawing).
	pickCam = pickCamView(mx, my)
	pickCon = pickControlPanel(mx, my)
	
	// camera navigation, control interaction.
	
	REMSTART - BLOCK OVERVIEW
		if activeCam = -1
			if omc = MCLICK_NONE
				if mc <> MCLICK_NONE
					>>> mouse down, pick a camera/control.
				endif
			else
				if omc = MCLICK_PRIMARY
					if mc = MCLICK_PRIMARY
						if activeCon <> CON_NONE
							>>> handle ongoing interaction with ui.
						endif
					else
						if mc = MCLICK_NONE
							>>> mouse up, react to clicked buttons and end ui interaction.
						endif
					endif
				endif
			endif
		else
			if omc <> MCLICK_NONE
				if mc = MCLICK_NONE
					>>> mouse up, end camera navigation.
				else
					>>> handle ongoing camera navigation.
				endif
			endif
		endif
	REMEND
	
	// (activeCam is -1 when no camera being interacted with.)
	if activeCam = -1
		if omc = MCLICK_NONE
			if mc <> MCLICK_NONE
				// mouse down.
				// only activate camera if mouse not in control panel.
				if pickCon <> -1
					activeCon = pickCon
				else
					// activate camera navigation if right- or wheel-clicked camera view.
					if (mc && (MCLICK_SECONDARY || MCLICK_WHEEL))
						activeCam = pickCam
						hide mouse
						bMouseVisible = 0
					endif
				endif
			endif
		else
			if omc = MCLICK_PRIMARY
				if mc = MCLICK_PRIMARY
					if activeCon <> CON_NONE
						GetControlBox(activeCon)
						select activeCon
							case CON_FOV
								cam(testCam).fov = clamp( Lerp(minFov, maxFov, 1.0 / __box2d.w * (mx -__box2d.x1)), minFov, maxFov )
								UpdateCam(testCam, 1, 0, 0, 0)
							endcase
							case CON_ASPECT
								if autoAspect = 0
									cam(testCam).aspect = clamp( Lerp(minAspect, maxAspect, 1.0 / __box2d.w * (mx -__box2d.x1)), minAspect, maxAspect )
									UpdateCam(testCam, 0, 1, 0, 0)
								endif
							endcase
							case CON_NEAR
								cam(testCam).rangeNear = clamp( Lerp(minRange, maxRange, ( 1.0 / __box2d.w * max(0, mx -__box2d.x1) ) ^ 10.0 ), minRange, maxRange )
								cam(testCam).rangeNear = min( cam(testCam).rangeNear, cam(testCam).rangeFar )
								UpdateCam(testCam, 1, 0, 1, 0)
							endcase
							case CON_FAR
								cam(testCam).rangeFar = clamp( Lerp(minRange, maxRange, ( 1.0 / __box2d.w * max(0, mx -__box2d.x1) ) ^ 10.0 ), minRange, maxRange )
								cam(testCam).rangeFar = max( cam(testCam).rangeNear, cam(testCam).rangeFar )
								UpdateCam(testCam, 1, 0, 1, 0)
							endcase
							case CON_CAMHEIGHT, CON_CAMWIDTH
								cam(testCam).view.y1 = clamp(my+10, cam(testCam).view.y2 - cam(testCam).view.w / minAspect, cam(testCam).view.y2 - cam(testCam).view.w / maxAspect)
								cam(testCam).view.y1 = min( max(10 + panel.h, cam(testCam).view.y1), cam(testCam).view.y2 - 64 )
								cam(testCam).view.h = cam(testCam).view.y2 - cam(testCam).view.y1
								if activeCon = CON_CAMWIDTH
									cam(testCam).view.x2 = clamp(mx+10, cam(testCam).view.x1 + cam(testCam).view.h * minAspect, cam(testCam).view.x1 + cam(testCam).view.h * maxAspect)
									cam(testCam).view.x2 = max( min(scrW-10, cam(testCam).view.x2), cam(testCam).view.x1 + panelLeftPad + panelRightPad + 64 )
									cam(testCam).view.w = cam(testCam).view.x2 - cam(testCam).view.x1
								endif
								// camera view changed, need to delete and remake in order to continue sending it to image.
								// grab current position and rotation.
								tmpPos as vec3d : tmpRot as vec3d
								tmpPos.x = camera position x(testCam) : tmpPos.y = camera position y(testCam) : tmpPos.z = camera position z(testCam)
								tmpRot.x = camera angle x(testCam)    : tmpRot.y = camera angle y(testCam)    : tmpRot.z = camera angle z(testCam)
								// delete remake, and restore properties.
								delete camera testCam
								make camera testCam
								set current camera mainCam
								position camera testCam, tmpPos.x, tmpPos.y, tmpPos.z
								rotate camera testCam, tmpRot.x, tmpRot.y, tmpRot.z
								color backdrop testCam, 0xffa0b0d0
								if autoAspect
									cam(testCam).aspect = cam(testCam).view.w / cam(testCam).view.h
								endif
								UpdateCam(testCam, 1, 1, 1, 1)
								// finally, re-set rendering to image.
								set camera to image testCam, imgTestCamRender, cam(testCam).view.w, cam(testCam).view.h
							endcase
							case CON_ZOOM
								targetMainCamOrbitDist = clamp( Lerp(0, 8192.0, 1.0 / __box2d.h * (my - __box2d.y1)), 0, 8192.0)
								mainCamOrbitDist = targetMainCamOrbitDist
							endcase
						endselect
					endif
				else
					if mc = MCLICK_NONE
						// mouse up.
						if activeCon = CON_AUTOASPECT
							autoAspect = 1 - autoAspect
							if autoAspect
								cam(testCam).aspect = cam(testCam).view.w / cam(testCam).view.h
								UpdateCam(testCam, 0, 1, 0, 0)
							endif
						endif
						activeCon = -1
					endif
				endif
			endif
		endif
	else
		if omc <> MCLICK_NONE
			if mc = MCLICK_NONE
				// mouse up. deactivate camera navigation and unhide mouse.
				activeCam = -1
				camRotSpeedX = 0
				camRotSpeedY = 0
				position mouse cmx, cmy
				show mouse
				bMouseVisible = 1
			else
				// slow rotation and movement speed for high-fov testCam.
				if activeCam = testCam
					// scale factor: min( 1.0, 0.0056 + (0.0111 * [1..179]) )
					//          -->  min( 1.0, 0.0056 + [0.0111 .. 1.9889]  )
					//          -->  min( 1.0, [0.0167 .. 1.9945]           )
					//          -->  [0.0167 .. 1.0].
					// hits 1.0x limit at 90 degrees, gets progressively slower towards 1 degree
					sf# = min(1.0, 0.0056 + (0.0111 * cam(testCam).fov))
				else
					sf# = 1.0
				endif
					
				if mc = MCLICK_SECONDARY
					// smooth rotate camera.
					camRotSpeedX = Lerp(camRotSpeedX, mmy * 0.6 * sf#, 0.4)
					camRotSpeedY = Lerp(camRotSpeedY, mmx * 0.6 * sf#, 0.4)
					
					rotate camera activeCam, camera angle x(activeCam) + camRotSpeedX, camera angle y(activeCam) + camRotSpeedY, 0
					
					// WASD navigation.
					move camera activeCam, ( kbW - kbS ) * 10.0 * sf#
					move camera right activeCam, ( kbD - kbA ) * 10.0 * sf#
				endif
				if mc = MCLICK_WHEEL
					// mixed mouse pan and WASD navigation.
					move camera activeCam, ( kbW - kbS ) * 10.0 * sf#
					move camera up activeCam, -mmy * 2.0 * sf#
					move camera right activeCam, mmx * 2.0 + ( kbD - kbA ) * 10.0 * sf#
				endif
			endif
		endif
	endif
	
	// zoom mainCam, if mouse is over it.
	if pickCam = mainCam
		// we will accumulate (in zoomAcc#) the total mousewheel scroll, and change zoom level when it totals one or more scrolls.
		zoomAcc# = zoomAcc# + mmz/120.0
		if zoomAcc# < -1.0 or zoomAcc# > 1.0
			tmp = zoomAcc#
			zoomDir = -sgn(tmp)
			zoomAcc# = zoomAcc# - tmp
			if zoomAcc# < 0 and targetMainCamOrbitDist = 0 then targetMainCamOrbitDist = 0.5
			while tmp <> 0
				targetMainCamOrbitDist = targetMainCamOrbitDist + 8192.0 * (1.0/16384.0*targetMainCamOrbitDist+0.01)^1.5 * zoomDir
				tmp = tmp - (tmp>0) + (tmp<0)
			endwhile
		endif
		targetMainCamOrbitDist = clamp(targetMainCamOrbitDist, 0, 8192.0)
		
		// if the target distance reaches 0, snap mainCam to a first-person view.
		if targetMainCamOrbitDist < 0.5
			targetMainCamOrbitDist = 0
			mainCamOrbitDist = 0
		endif
	endif
	
	// smooth current mainCam dist to target dist.
	mainCamOrbitDist = Lerp(mainCamOrbitDist, targetMainCamOrbitDist, 0.4)
	
	// push mainCam back into orbit.
	move camera mainCam, -mainCamOrbitDist
	
	
	// - cam data copying -
	// perform only if ctrl key held, then key pressed.
	if okbCtrl and kbCtrl
		if okbC = 0 and kbC
			d$ = "[position]" + crlf$() + "x = " + str$(camera position x(testCam), 4) + crlf$() + "y = " + str$(camera position y(testCam), 4) + crlf$() + "z = " + str$(camera position z(testCam), 4) + crlf$()
			d$ = d$ + "[rotation]" + crlf$() + "x = " + str$(camera angle x(testCam), 4) + crlf$() + "y = " + str$(camera angle y(testCam), 4) + crlf$() + "z = " + str$(camera angle z(testCam), 4) + crlf$()
			d$ = d$ + "[settings]" + crlf$() + "fov    = " + str$(cam(testCam).fov, 4) + crlf$() + "aspect = " + str$(cam(testCam).aspect, 4) + crlf$() + "near   = " + str$(cam(testCam).rangeNear, 4) + crlf$() + "far    = " + str$(cam(testCam).rangeFar, 4) + crlf$()
			d$ = d$ + "[view]" + crlf$() + "w = " + str$(cam(testCam).view.w) + crlf$() + "h = " + str$(cam(testCam).view.h)
			write to clipboard d$
		endif
	endif
	
	
	
	// - render -
	// note that drawing is designed to handle working both with and without d3dfunc available.
	
	// - update testCam's visible fustrum -
	UpdateFustrum(testCam, objFustrum)
	
	// - control panel -
	
	// control panel background and draggable testCam viewport resize splitters.
	
	GetControlBox(CON_NONE)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	
	GetControlBox(CON_CAMHEIGHT)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	
	GetControlBox(CON_CAMWIDTH)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	
	// overlay showing max testCam view dimensions given current dimensions and min/max allowed aspect ratio.
	
	__box2d = cam(testCam).view		// (saves on typing/reading 'cam(testCam).view' over and over!)
	
	if activeCon = CON_CAMWIDTH or activeCon = CON_CAMHEIGHT
		Draw_Box(__box2d.x1, __box2d.y2 - __box2d.w / minAspect, __box2d.x1 + __box2d.h * maxAspect, __box2d.y2, 0, 0x08ffffff, 0, 0, 0)
	endif
	
	// testcam info: dimensions, position, orientation.
	
	text panel.x1+1, panel.y2-17, str$(cam(testCam).view.w) + "x" + str$(cam(testCam).view.h)
	center text panel.x1+panel.w/2, __box2d.y2-15, "pos: " + str$(camera position x(testCam),1) + ", " + str$(camera position y(testCam),1) + ", " + str$(camera position z(testCam),1) + "    rot: " + ", " + str$(camera angle x(testCam),1) + ", " + str$(camera angle y(testCam),1) + ", " + str$(camera angle z(testCam),1)
	
	// controls
	// slider controls have a transparent background, a slider handle, an outline, and a text label.
	// despite the large amount of stuff, these four blocks are all doing the same simple things: find out where the control is; draw stuff in it.
	
	GetControlBox(CON_FOV)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	x = __box2d.x1 + __box2d.w / (maxFov-minFov) * (cam(testCam).fov-minFov)
	Draw_Box(x-2, __box2d.y1, x+2, __box2d.y1+__box2d.h, 0, 0x40ffffff, 0, 0, 0)
	if pickCon = CON_FOV then Draw_BoxOutline(__box2d.x1, __box2d.y1, __box2d.x2-1, __box2d.y2-1, 0, 0x40ffffff, 0, 0, 0)
	label$ = "fov    " + padleft$( str$(cam(testCam).fov, 1), 10 )
	text __box2d.x1 - 3 - text width(label$), __box2d.y1 - 1, label$
	
	GetControlBox(CON_ASPECT)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff * (1-autoAspect), 0, 0, 0)
	x = __box2d.x1 + __box2d.w / (maxAspect-minAspect) * (cam(testCam).aspect-minAspect)
	Draw_Box(x-2, __box2d.y1+autoAspect*4, x+2, __box2d.y1+__box2d.h-autoAspect*4, 0, 0x40ffffff, 0, 0, 0)
	if pickCon = CON_ASPECT and autoAspect = 0 then Draw_BoxOutline(__box2d.x1, __box2d.y1, __box2d.x2-1, __box2d.y2-1, 0, 0x40ffffff, 0, 0, 0)
	label$ = "aspect " + padleft$( str$(cam(testCam).aspect, 4), 10 )
	text __box2d.x1 - 3 - text width(label$), __box2d.y1 - 1, label$
	
	GetControlBox(CON_NEAR)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	x = Lerp( __box2d.x1, __box2d.x2, (1.0 / (maxRange-minRange) * (cam(testCam).rangeNear-minRange)) ^ 0.1 )
	Draw_Box(x-2, __box2d.y1, x+2, __box2d.y1+__box2d.h, 0, 0x40ffffff, 0, 0, 0)
	if pickCon = CON_NEAR then Draw_BoxOutline(__box2d.x1, __box2d.y1, __box2d.x2-1, __box2d.y2-1, 0, 0x40ffffff, 0, 0, 0)
	label$ = "near   " + padleft$( str$(cam(testCam).rangeNear, 4), 10 )
	text __box2d.x1 - 3 - text width(label$), __box2d.y1 - 1, label$
	
	GetControlBox(CON_FAR)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	x = Lerp( __box2d.x1, __box2d.x2, (1.0 / (maxRange-minRange) * (cam(testCam).rangeFar-minRange)) ^ 0.1 )
	Draw_Box(x-2, __box2d.y1, x+2, __box2d.y1+__box2d.h, 0, 0x40ffffff, 0, 0, 0)
	if pickCon = CON_FAR then Draw_BoxOutline(__box2d.x1, __box2d.y1, __box2d.x2-1, __box2d.y2-1, 0, 0x40ffffff, 0, 0, 0)
	label$ = "far    " + padleft$( str$(cam(testCam).rangeFar, 4), 10 )
	text __box2d.x1 - 3 - text width(label$), __box2d.y1 - 1, label$
	
	// auto aspect button is made faster by using a pre-generated small text image rather than switching text modes on the fly.
	// button toggle states distinguished by shifting text image 1,1 pixels when on, eg. calc'd pos + autoAspect.
	
	GetControlBox(CON_AUTOASPECT)
	Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x40ffffff, 0, 0, 0)
	paste image imgAuto, __box2d.x1 + __box2d.w/2 - image width(imgAuto)/2 + autoAspect, __box2d.y1 + __box2d.h/2 - image height(imgAuto)/2 + autoAspect, 1
	
	// mainCam zoom level indicator
	// has a vertical meter and a circle outline indicating current zoom.
	// if camera has snapped to a first-person view, indicated with a filled circle.
	// if mouse is over the control, indicated with a transparent background.
	// the meter is marked at intervals of increasing distance. (hidden if panel disabled)
	
	GetControlBox(CON_ZOOM)
	Draw_Line(__box2d.x2, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x80ffffff, 0)
	Draw_Circle(__box2d.x1 + 4, __box2d.y1 + __box2d.h / 8192.0 * mainCamOrbitDist, 4, clamp(1-mainCamOrbitDist, 0, 1), 0xffffffff)
	if mainCamOrbitDist = 0 then Draw_Circle(__box2d.x1 + 4, __box2d.y1, 5, 0, 0x80ffffff)
	if pickCon = CON_ZOOM then Draw_Box(__box2d.x1, __box2d.y1, __box2d.x2, __box2d.y2, 0, 0x20ffffff, 0, 0, 0)
	if panelEnabled
		for z = 0 to 13
			y = __box2d.y1 + __box2d.h / 8192.0 * 2^z
			Draw_Line(__box2d.x2-6, y, __box2d.x2, y, 0, 0x80ffffff, 0)
		next z
	endif
	
	// prompt (appears in top-right of screen, is transparent to reduce the distraction of it flickering between different states).
	prompt$ = "Mouse1 to interact with controls. Mouse2/MouseWheel Button + WASD to navigate. Mousewheel adjusts main camera zoom. Tab toggles UI. Ctrl+C copies camdata."
	tmp = pickCon
	if tmp = CON_FOV        then prompt$ = "adjust Field Of View                         0.1 .. 179° "
	if tmp = CON_ASPECT     then prompt$ = "adjust Aspect Ratio                        1:0.5 .. 1:5  "
	if tmp = CON_NEAR       then prompt$ = "adjust Near Plane Distance                0.0001 .. 65535"
	if tmp = CON_FAR        then prompt$ = "adjust Far Plane Distance                 0.0001 .. 65535"
	if tmp = CON_AUTOASPECT then prompt$ = "toggle automatic Aspect Ratio control   click to turn " + left$("on ", 3*(autoAspect=0))  + left$("off", 3*(autoAspect=1))
	if tmp = CON_CAMHEIGHT  then prompt$ = "adjust Camera Viewport Height                            "
	if tmp = CON_CAMWIDTH   then prompt$ = "adjust Camera Viewport Size                              "
	if tmp = CON_ZOOM       then prompt$ = "adjust Main Camera Zoom Level                  0 .. 8192 "
	ink 0x80ffffff
	text scrW - text width(prompt$), 0, prompt$
	ink 0xffffffff
	
	// if navigating and mouse is hidden, indicate its return position to help user get bearings when time to retake mouse.
	// (outermost circles only worthwhile if transparent, so only drawing if using d3d for now)
	if bMouseVisible = 0
		Draw_Circle(cmx, cmy, 3, 0, 0xb0ffffff)
		Draw_Circle(cmx, cmy, 4, 0, 0x60000000)
		D3DFUNC_BLOCKSTART
			Draw_Circle(cmx, cmy, 7, 0, 0x30ffffff)
			Draw_Circle(cmx, cmy, 18, 0, 0x10000000)
			Draw_Circle(cmx, cmy, 40, 0, 0x08000000)
			Draw_Circle(cmx, cmy, 160, 0, 0x04ffffff)
		D3DFUNC_BLOCKEND
	endif
	
	// sync
	// mainCam will render to screen, testCam to its image.
	sync
	
	// (drawing d3d_xxx3d stuff after sync avoids a very noticeable lag effect.)
	
	// testCam position eye marker
	GetCameraNormal(testCam)
	cx# = camera position x(testCam) : cy# = camera position y(testCam) : cz# = camera position z(testCam)
	vx# = __vec3d.x : vy# = __vec3d.y : vz# = __vec3d.z
	Draw_Circle3d(mainCam, 10.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xff080808, 1)
	Draw_Circle3d(mainCam,  8.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xff080808, 1)
	Draw_Circle3d(mainCam,  9.0, cx#, cy#, cz#, vx#, vy#, vz#, 0xfff7f7f7, 1)
	Draw_AAPoint3d(mainCam, 1.0, cx#, cy#, cz#, 0xff080808, 0xff080808, 0xff080808, 0)
	Draw_Line3d(mainCam, cx#, cy#, cz#, cx#+vx#*cam(testCam).rangeNear, cy#+vy#*cam(testCam).rangeNear, cz#+vz#*cam(testCam).rangeNear, 0, 0xff080808, 0, 1)
	
	// extra circle on marker indicating testCam's top side
	GetCameraUp(testCam)
	ux# = __vec3d.x : uy# = __vec3d.y : uz# = __vec3d.z
	Draw_Line3d(mainCam, cx#, cy#, cz#, cx#+ux#*8.0, cy#+uy#*8.0, cz#+uz#*8.0, 0, 0xff080808, 0, 1)
	Draw_Circle3d(mainCam, 1.0, cx#+ux#*9.0, cy#+uy#*9.0, cz#+uz#*9.0, vx#, vy#, vz#, 0xff080808, 1)
	
	// mainCam orbit centre marker
	move camera mainCam, mainCamOrbitDist
	cx# = camera position x(mainCam) : cy# = camera position y(mainCam) : cz# = camera position z(mainCam)
	move camera mainCam, -mainCamOrbitDist
	Draw_AAPoint3d(mainCam, 81.920 * (1.0/8192.0*mainCamOrbitDist)^1, cx#, cy#, cz#, 0xffb02020, 0xff20b020, 0xff2020b0, 0)
	Draw_AAPoint3d(mainCam, 81, cx#, cy#, cz#, 0x20b02020, 0x2020b020, 0x202020b0, 0)
	
	// now draw testCam image to screen. We do this after the sync so that
	// we are guaranteed to have a usable image (if we've resized the view, the
	// camera will have been deleted and remade and the image blanked, so the
	// first sync after would flash a black frame). It does mean the view is
	// always one frame behind, but this isn't really an issue...
	paste image imgTestCamRender, cam(testCam).view.x1, cam(testCam).view.y1
	
	// ...although we have to defer updating the control panel's position to
	// keep it a frame behind and thus in sync with the camera view. (we can do
	// this anytime after the panel been drawn for the current frame, but doing
	// it here, we have continuity in the comments.)
	UpdateControlPanel()
	
	// avoid needlessly overworking your cpu!
	nice wait 20
LOOP



// -- function block  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --


// Linear intERPolate.
function Lerp(a as float, b as float, t as float)
	a = a + (b-a) * t
endfunction a


// returns either -1 or 1, at random:
//  out = 1 - [0,1] * 2
//      = 1 - [0,2]
//      = [1,-1]
function RndSgn()
	out = 1 - rnd(1)*2
endfunction out


// calculates the cross product (perpendicular vector) of two vectors.
function CrossProduct(a as vec3d, b as vec3d)
	__vec3d.x = a.y * b.z - a.z * b.y
	__vec3d.y = a.z * b.x - a.x * b.z
	__vec3d.z = a.x * b.y - a.y * b.x
endfunction


// normalises a vector (divides its components by its length, to make it unit (=1) length).
function Normalise(in as vec3d)
	length# = sqrt(in.x*in.x + in.y*in.y + in.z*in.z)
	__vec3d.x = in.x / length#
	__vec3d.y = in.y / length#
	__vec3d.z = in.z / length#
endfunction


// prepares cam data for specified camera.
function SetCamData(id, fov#, asp#, near#, far#, vx1#, vy1#, vx2#, vy2#)
	local out as t_camData
	
	out.fov       = fov#
	out.aspect    = asp#
	out.rangeNear = near#
	out.rangeFar  = far#
	out.view.x1   = vx1#
	out.view.y1   = vy1#
	out.view.x2   = vx2#
	out.view.y2   = vy2#
	out.view.w    = vx2# - vx1#
	out.view.h    = vy2# - vy1#
	
	cam(id) = out
endfunction


// updates physical camera from its cam settings.
function UpdateCam(id, bFov, bAspect, bRange, bView)
	local camData as t_camData
	camData = cam(id)
	
	if bFov    then set camera fov    id, camData.fov
	if bAspect then set camera aspect id, camData.aspect
	if bRange  then set camera range  id, camData.rangeNear, camData.rangeFar
	if bView   then set camera view   id, camData.view.x1, camData.view.y1, camData.view.x2, camData.view.y2
endfunction


function GetCameraRight(id)
	GetCameraDirection(id, 1, 0, 0)
endfunction

function GetCameraUp(id)
	GetCameraDirection(id, 0, 1, 0)
endfunction

function GetCameraNormal(id)
	GetCameraDirection(id, 0, 0, 1)
endfunction

function GetCameraDirection(id, bRight, bUp, bNormal)
	local option as dword
	
	// convert three direction choice parameters into single value, cancel if anything other than exactly one direction specified.
	option = bRight + (bUp*2) + (bNormal*4)
	if option = 0 or option = %011 or option > %100 then exitfunction
	
	D3DFUNC_BLOCKSTART
		select option
			case %001 : d3d_get_camera_right id  : endcase
			case %010 : d3d_get_camera_up id     : endcase
			case %100 : d3d_get_camera_normal id : endcase
		endselect
		__vec3d.x = d3d_vector_x() : __vec3d.y = d3d_vector_y() : __vec3d.z = d3d_vector_z()
	D3DFUNC_BLOCKEND
	
	NOD3DFUNC_BLOCKSTART
		local oldPos as vec3d
		oldPos.x = camera position x(id) : oldPos.y = camera position y(id) : oldPos.z = camera position z(id)
		position camera id, 0, 0, 0
		select option
			case %001 : move camera right id, 1.0 : endcase
			case %010 : move camera up id, 1.0    : endcase
			case %100 : move camera id, 1.0       : endcase
		endselect
		__vec3d.x = camera position x(id) : __vec3d.y = camera position y(id) : __vec3d.z = camera position z(id)
		position camera id, oldPos.x, oldPos.y, oldPos.z
	NOD3DFUNC_BLOCKEND
endfunction


function UpdateFustrum(id, obj)
	local camData as t_camData
	local DF as float			// DF and DN are the half-heights
	local DN as float			//  of the near and far planes.
	
	local FarTL as vec3d		// calculated coords of the
	local FarTR as vec3d		//  four corners of the far plane...
	local FarBR as vec3d
	local FarBL as vec3d
	
	local NearTL as vec3d		// ... and those of the near plane.
	local NearTR as vec3d
	local NearBR as vec3d
	local NearBL as vec3d
	
	camData = cam(id)
	
	// DF = tan(fov/2)*dist; DN is somewhere between 0 and DF.
	DF = tan( camData.fov * 0.5 ) * camData.rangeFar
	DN = Lerp( 0, DF, 1.0 / camData.rangeFar * camData.rangeNear )
	
	//  * the four corners of a plane:
	//     * top-left			-w#   h#
	//     * top-right			 w#   h#
	//     * bottom-right		 w#  -h#
	//     * bottom-left		-w#  -h#
	//    where w# and h# are the half-width and half-height of the plane,
	//    and in combination make the offsets of the corners from its centre.
	
	EZRO_BLOCKSTART
	
		// - USING EZROTATE -
		// calculation process:
		//  * pass camera position/rotation to EZrotate
		//  * The coordinates in 3d world space are found by:
		//     * adding the z-component (near and far plane distances) to the above
		//     * placing them in camera space, ie. (-w#,h#,z#) from camera's pov
		//     * converting that to world space.
		//  EZrotate handles this for us, example:
		//   * we tell it the camera is at position 0,0,10 and orientation 90,0,0
		//   * we give it camera space coords 0,0,5
		//   * it gives us world space coords 0,-5,10
		
		// pass cameras (euler angle) rotation and position to EZrotate.
		EZro_SetEuler camera angle x(id), camera angle y(id), camera angle z(id)
		EZro_SetPos camera position x(id), camera position y(id), camera position z(id)
		
		// calculate the four corners of the far plane first.
		w# = DF * camData.aspect
		h# = DF
		
		EZRo_FindPointFromOffset -w#,  h#, camData.rangeFar    : FarTL.x  = EZro_GetOffsetX() : FarTL.y  = EZro_GetOffsetY() : FarTL.z  = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset  w#,  h#, camData.rangeFar    : FarTR.x  = EZro_GetOffsetX() : FarTR.y  = EZro_GetOffsetY() : FarTR.z  = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset  w#, -h#, camData.rangeFar    : FarBR.x  = EZro_GetOffsetX() : FarBR.y  = EZro_GetOffsetY() : FarBR.z  = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset -w#, -h#, camData.rangeFar    : FarBL.x  = EZro_GetOffsetX() : FarBL.y  = EZro_GetOffsetY() : FarBL.z  = EZro_GetOffsetZ()
		
		// and now the near plane.
		w# = DN * camData.aspect
		h# = DN
		
		EZRo_FindPointFromOffset -w#,  h#, camData.rangeNear   : NearTL.x = EZro_GetOffsetX() : NearTL.y = EZro_GetOffsetY() : NearTL.z = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset  w#,  h#, camData.rangeNear   : NearTR.x = EZro_GetOffsetX() : NearTR.y = EZro_GetOffsetY() : NearTR.z = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset  w#, -h#, camData.rangeNear   : NearBR.x = EZro_GetOffsetX() : NearBR.y = EZro_GetOffsetY() : NearBR.z = EZro_GetOffsetZ()
		EZRo_FindPointFromOffset -w#, -h#, camData.rangeNear   : NearBL.x = EZro_GetOffsetX() : NearBL.y = EZro_GetOffsetY() : NearBL.z = EZro_GetOffsetZ()
		
	EZRO_BLOCKEND
	NOEZRO_BLOCKSTART
	
		// - USING D3D OR DBP -
		// calculation process:
		//  * get camera forward ('normal'), up, and right directional vectors.
		//  * multiply the desired offset (from the camera PoV, ie. as though the camera were at the origin and unrotated in world space)
		//    by these directional vectors.
		//  * add to the camera's position.
		// An example (finding same point for same camera as in ezrotate example above):
		//  * the camera is at position 0,0,10, orientation 90,0,0
		//  * d3d_get_camera_xxx calls give us:
		//     * right vector: 1, 0, 0 (camera 'right' (+x) coincides with world space 'right' (+x); this makes sense - cam looked down, but didn't turn to left or right)
		//     * up vector:    0, 0, 1 (that is, 'up' (+y) for the camera is 'forwards' (+z) in world space; if you look down, the top of your head points forwards)
		//     * fwd vector:   0,-1, 0 (that is, the camera is looking down, so forwards from it's PoV is downwards in world space)
		//    in summary, cam:forward -> world:down, cam:up -> world:forward, and cam:right -> world:right.
		//  * we want the point 0,0,5 relative to the camera
		//    that is, the point is at (0)   (1, 0, 0)   (0*1  0*0   0*0)   (0, 0, 0)
		//                             (0) * (0, 0, 1) = (0*0  0*0   0*1) = (0, 0, 0) 
		//                             (5)   (0,-1, 0)   (5*0  5*-1  5*0)   (0,-5, 0)
		//                                                                  ---------
		//                                                                  (0,-5, 0)
		//    offset in column vector times 3x3 matrix where top row is
		//    right vector (1,0,0), middle row is up (0,0,1), and bottom row forward (0,-1,0).
		//    the resulting vector has x equal to the sum of the first matrix column,
		//    y equal to sum of the second, and z equal to sum of the third - in this
		//    case:
		//     * x = 0+0+0 = 0      * y = 0+0+(-5) = -5      * z = 0+0+0 = 0
		//  * finally, remember to add the camera coords:
		//     * x = 0 + 0 = 0      * y = -5 + 0 = -5        * z = 0 + 10 = 10
		//  * SO, the point is at 0,-5,10 in world space.
		//    in summary, position = camPos + offset * camDirection
		
		local camPos as vec3d
		local camRight as vec3d
		local camUp as vec3d
		local camFwd as vec3d
		local camDirection as vec3d
		
		// store camera pos.
		camPos.x = camera position x(id)
		camPos.y = camera position y(id)
		camPos.z = camera position z(id)
		
		// fill camera direction vectors.
		GetCameraRight(id)  : camRight.x = __vec3d.x : camRight.y = __vec3d.y : camRight.z = __vec3d.z
		GetCameraUp(id)     : camUp.x    = __vec3d.x : camUp.y    = __vec3d.y : camUp.z    = __vec3d.z
		GetCameraNormal(id) : camFwd.x   = __vec3d.x : camFwd.y   = __vec3d.y : camFwd.z   = __vec3d.z
		
		// calculate the four corners of the far plane first.
		w# = DF * camData.aspect
		h# = DF
		
		FarTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
		FarTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
		FarTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
		
		FarTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeFar * camFwd.x
		FarTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeFar * camFwd.y
		FarTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeFar * camFwd.z
		
		FarBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
		FarBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
		FarBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
		
		FarBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeFar * camFwd.x
		FarBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeFar * camFwd.y
		FarBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeFar * camFwd.z
		
		// and now the near plane.
		w# = DN * camData.aspect
		h# = DN
		
		NearTL.x = camPos.x + (-w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
		NearTL.y = camPos.y + (-w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
		NearTL.z = camPos.z + (-w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
		
		NearTR.x = camPos.x + (w#) * camRight.x + h# * camUp.x + camData.rangeNear * camFwd.x
		NearTR.y = camPos.y + (w#) * camRight.y + h# * camUp.y + camData.rangeNear * camFwd.y
		NearTR.z = camPos.z + (w#) * camRight.z + h# * camUp.z + camData.rangeNear * camFwd.z
		
		NearBR.x = camPos.x + (w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
		NearBR.y = camPos.y + (w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
		NearBR.z = camPos.z + (w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
		
		NearBL.x = camPos.x + (-w#) * camRight.x + (-h#) * camUp.x + camData.rangeNear * camFwd.x
		NearBL.y = camPos.y + (-w#) * camRight.y + (-h#) * camUp.y + camData.rangeNear * camFwd.y
		NearBL.z = camPos.z + (-w#) * camRight.z + (-h#) * camUp.z + camData.rangeNear * camFwd.z
		
	NOEZRO_BLOCKEND
	
	// finally, update the fustrum object.
	lock vertexdata for limb obj, 0
		
		// near plane
		set vertexdata position  0, NearTL.x, NearTL.y, NearTL.z
		set vertexdata position  1, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position  2, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position  3, NearBL.x, NearBL.y, NearBL.z
		
		// far plane
		set vertexdata position  7, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  6, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position  5, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position  4, FarBL.x,  FarBL.y,  FarBL.z
		
		// top face
		set vertexdata position  8, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position  9, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 10, NearTR.x, NearTR.y, NearTR.z
		set vertexdata position 11, NearTL.x, NearTL.y, NearTL.z
		
		// bottom face
		set vertexdata position 15, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 14, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 13, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 12, NearBL.x, NearBL.y, NearBL.z
		
		// right face
		set vertexdata position 16, FarTR.x,  FarTR.y,  FarTR.z
		set vertexdata position 17, FarBR.x,  FarBR.y,  FarBR.z
		set vertexdata position 18, NearBR.x, NearBR.y, NearBR.z
		set vertexdata position 19, NearTR.x, NearTR.y, NearTR.z
		
		// left face
		set vertexdata position 23, FarTL.x,  FarTL.y,  FarTL.z
		set vertexdata position 22, FarBL.x,  FarBL.y,  FarBL.z
		set vertexdata position 21, NearBL.x, NearBL.y, NearBL.z
		set vertexdata position 20, NearTL.x, NearTL.y, NearTL.z
		
	unlock vertexdata
	
	// recalc normals (lighting) and bounds (occlusion) so obj displays correctly.
	set object normals obj
	calculate object bounds obj
	
endfunction


// returns -1 if point not in camera view, else returns camera identifier.
function PickCamView(x, y)
	local out, camCount, camData as t_camData
	camCount = array count( cam() )
	
	out = -1
	
	for c = 0 to camCount
		camData = cam(c)
		if x > camData.view.x1 and y > camData.view.y1 and x < camData.view.x2 and y < camData.view.y2
			out = c
		endif
	next c
endfunction out


// returns 1 if true, else 0.
function PointInBox(x, y, testBox as box2d)
	if x > testBox.x1 and y > testBox.y1 and x < testBox.x2 and y < testBox.y2 then exitfunction 1
endfunction 0


// use to resync control panel position with changing testCam viewport.
function UpdateControlPanel()
	panel.w  = cam(testCam).view.w
	panel.h  = 84.0
	panel.x1 = cam(testCam).view.x1
	panel.y1 = cam(testCam).view.y1 - 2.0 - panel.h
	panel.x2 = panel.x1 + panel.w
	panel.y2 = panel.y1 + panel.h
endfunction

// returns -1 if point not in control panel, else returns control identifier.
function pickControlPanel(x, y)
	local out
	
	out = -1
	
	if PointInBox(x, y, panel)
		out = 0
		
		GetControlBox(CON_FOV)			: if PointInBox(x, y, __box2d) then out = CON_FOV
		GetControlBox(CON_ASPECT) 		: if PointInBox(x, y, __box2d) then out = CON_ASPECT
		GetControlBox(CON_NEAR)			: if PointInBox(x, y, __box2d) then out = CON_NEAR
		GetControlBox(CON_FAR)			: if PointInBox(x, y, __box2d) then out = CON_FAR
		GetControlBox(CON_AUTOASPECT)	: if PointInBox(x, y, __box2d) then out = CON_AUTOASPECT
		GetControlBox(CON_CAMHEIGHT)	: if PointInBox(x, y, __box2d) then out = CON_CAMHEIGHT
		GetControlBox(CON_CAMWIDTH)		: if PointInBox(x, y, __box2d) then out = CON_CAMWIDTH
	else
		GetControlBox(CON_ZOOM)			: if PointInBox(x, y, __box2d) then out = CON_ZOOM
	endif
endfunction out


// fills __box2d with control coords.
function GetControlBox(id)
	
	if panelEnabled
		select id
			case CON_NONE
				__box2d.x1 = panel.x1
				__box2d.y1 = panel.y1
				__box2d.x2 = panel.x2
				__box2d.y2 = panel.y2-20
			endcase
			case CON_FOV
				__box2d.x1 = panel.x1 + panelLeftPad
				__box2d.y1 = panel.y1 + 1
				__box2d.x2 = panel.x2 - panelRightPad
				__box2d.y2 = panel.y1 + 14
			endcase
			case CON_ASPECT
				__box2d.x1 = panel.x1 + panelLeftPad
				__box2d.y1 = panel.y1 + 16
				__box2d.x2 = panel.x2 - panelRightPad
				__box2d.y2 = panel.y1 + 29
			endcase
			case CON_NEAR
				__box2d.x1 = panel.x1 + panelLeftPad
				__box2d.y1 = panel.y1 + 31
				__box2d.x2 = panel.x2 - panelRightPad
				__box2d.y2 = panel.y1 + 44
			endcase
			case CON_FAR
				__box2d.x1 = panel.x1 + panelLeftPad
				__box2d.y1 = panel.y1 + 46
				__box2d.x2 = panel.x2 - panelRightPad
				__box2d.y2 = panel.y1 + 59
			endcase
			case CON_AUTOASPECT
				__box2d.x1 = panel.x2 - panelRightPad + 3
				__box2d.y1 = panel.y1 + 16
				__box2d.x2 = panel.x2 - 2
				__box2d.y2 = panel.y1 + 29
			endcase
			case CON_CAMHEIGHT
				__box2d.x1 = panel.x1
				__box2d.y1 = panel.y2 - 18
				__box2d.x2 = panel.x2 - 22
				__box2d.y2 = panel.y2
			endcase
			case CON_CAMWIDTH
				__box2d.x1 = panel.x2 - 20
				__box2d.y1 = panel.y2 - 18
				__box2d.x2 = panel.x2
				__box2d.y2 = panel.y2
			endcase
			case CON_ZOOM
				__box2d.x1 = scrW - 24
				__box2d.y1 = scrH - 522
				__box2d.x2 = scrW - 10
				__box2d.y2 = scrH - 10
			endcase
		endselect
	else
		select id
			case CON_NONE, CON_FOV, CON_ASPECT, CON_NEAR, CON_FAR, CON_AUTOASPECT, CON_CAMHEIGHT, CON_CAMWIDTH
				__box2d.x1 = -1000
				__box2d.y1 = -1000
				__box2d.x2 = -1000
				__box2d.y2 = -1000
			endcase
			case CON_ZOOM
				__box2d.x1 = scrW - 24
				__box2d.y1 = scrH - 522
				__box2d.x2 = scrW - 10
				__box2d.y2 = scrH - 10
			endcase
		endselect
	endif
	
	__box2d.w = __box2d.x2 - __box2d.x1
	__box2d.h = __box2d.y2 - __box2d.y1
	
endfunction


// Drawing function wrappers. Use d3d if available, otherwise DBP/M1U.
// where bSetAllColors is 0, draws with solid color c1, otherwise draws with c1,c2,c3,c4 -> tl,tr,br,bl
function Draw_Box(x1, y1, x2, y2, bSetAllColors, c1 as dword, c2 as dword, c3 as dword, c4 as dword)
	if (x1 = x2 and y1 = y2) or (x1 < 0 and x2 < 0) or (y1 < 0 and y2 < 0) or (x1 > scrW and x2 > scrW) or (y1 > scrH and y2 > scrH) then exitfunction
	if bSetAllColors
		D3DFUNC_BLOCKSTART
			d3d_box x1, y1, x2, y2, c1, c2, c3, c4
		D3DFUNC_BLOCKEND
		NOD3DFUNC_BLOCKSTART
			box x1, y1, x2, y2, c1, c2, c3, c4
		NOD3DFUNC_BLOCKEND
	else
		D3DFUNC_BLOCKSTART
			d3d_box x1, y1, x2, y2, c1
		D3DFUNC_BLOCKEND
		NOD3DFUNC_BLOCKSTART
			box x1, y1, x2, y2, c1, c1, c1, c1
		NOD3DFUNC_BLOCKEND
	endif
endfunction

function Draw_BoxOutline(x1, y1, x2, y2, bSetAllColors, c1 as dword, c2 as dword, c3 as dword, c4 as dword)
	if (x1 = x2 and y1 = y2) or (x1 < 0 and x2 < 0) or (y1 < 0 and y2 < 0) or (x1 > scrW and x2 > scrW) or (y1 > scrH and y2 > scrH) or (x1 >= 0 and x2 < scrW and y1 >= 0 and y2 < scrH)=0 then exitfunction
	D3DFUNC_BLOCKSTART
		if bSetAllColors = 0 then c2 = c1 : c3 = c1 : c4 = c1
		d3d_line x1, y1, x2, y1, c1, c2
		d3d_line x1, y1, x1, y2, c1, c4
		d3d_line x2, y1, x2, y2, c2, c3
		d3d_line x1, y2, x2, y2, c4, c3
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		local oldColor
		oldColor = get foreground color()
		ink c1
		line x1, y1, x2, y1`, c1, c2
		line x1, y1, x1, y2`, c1, c4
		line x2, y1, x2, y2`, c2, c3
		line x1, y2, x2, y2`, c4, c3
		ink oldColor
	NOD3DFUNC_BLOCKEND
endfunction

// (can only do single-colour line with non-d3d line)
function Draw_Line(x1, y1, x2, y2, bSetAllColors, c1 as dword, c2 as dword)
	if x1 = x2 and y1 = y2 then exitfunction
	D3DFUNC_BLOCKSTART
		if bSetAllColors
			  d3d_line x1, y1, x2, y2, c1, c2
		else
			  d3d_line x1, y1, x2, y2, c1
		endif
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		local oldColor as dword
		oldColor = get foreground color()
		if c1 <> oldColor
			ink c1
			line x1, y1, x2, y2`, c1
			ink c2
		else
			line x1, y1, x2, y2`, c1
		endif
	NOD3DFUNC_BLOCKEND
endfunction

function Draw_Circle(x, y, r, bFilled, color as dword)
	Draw_Ellipse(x, y, r, r, bFilled, color)
endfunction

function Draw_Ellipse(x, y, rx, ry, bFilled, color as dword)
	if x + rx < 0 or y + ry < 0 or x - rx > scrW or y - ry > scrH then exitfunction
	D3DFUNC_BLOCKSTART
		d3d_ellipse x, y, rx, ry, bFilled, color
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		if bFilled
			fill ellipse x, y, rx, ry, color
		else
			local oldColor as dword
			oldColor = get foreground color()
			if color <> oldColor
				ink color, 0
				ellipse x, y, rx, ry
				ink oldColor, 0
			else
				ellipse x, y, rx, ry
			endif
		endif
	NOD3DFUNC_BLOCKEND
endfunction

// (doesn't support size in non-d3d as current)
function Draw_Dot3d(camId, x#, y#, z#, size#, color as dword, zEnable)
	D3DFUNC_BLOCKSTART
		d3d_dot3d camId, x#, y#, z#, size#, color, zEnable
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		local x1, y1, x2, y2
		position object objMarker, x#, y#, z#
		x = object screen x(objMarker) : y = object screen y(objMarker)
		dot x, y, color
	NOD3DFUNC_BLOCKEND
endfunction

function Draw_AAPoint3d(camId, size#, x#, y#, z#, colorx as dword, colory as dword, colorz as dword, zEnable)
		Draw_Line3D(camId, x#-size#, y#, z#, x#+size#, y#, z#, 1, colorx, colorx, zenable)
		Draw_Line3D(camId, x#, y#-size#, z#, x#, y#+size#, z#, 1, colory, colory, zenable)
		Draw_Line3D(camId, x#, y#, z#-size#, x#, y#, z#+size#, 1, colorz, colorz, zenable)
endfunction

function Draw_Line3d(camId, x1#, y1#, z1#, x2#, y2#, z2#, bSetAllColors, c1 as dword, c2 as dword, zEnable)
	D3DFUNC_BLOCKSTART
		if bSetAllColors
			d3d_line3d camId, x1#, y1#, z1#, x2#, y2#, z2#, c1, c2, zEnable
		else
			d3d_line3d camId, x1#, y1#, z1#, x2#, y2#, z2#, c1, c1, zEnable
		endif
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		local x1, y1, x2, y2, oldColor
		position object objMarker, x1#, y1#, z1#
		if object in screen(objMarker)
			x1 = object screen x(objMarker) : y1 = object screen y(objMarker)
			position object objMarker, x2#, y2#, z2#
			if object in screen(objMarker)
				x2 = object screen x(objMarker) : y2 = object screen y(objMarker)
				oldColor = get foreground color()
				if c1 <> oldColor
					ink c1
					line x1, y1, x2, y2
					ink oldColor
				else
					line x1, y1, x2, y2
				endif
			endif
		endif
	NOD3DFUNC_BLOCKEND
endfunction

function Draw_Circle3d(camId, r#, x#, y#, z#, nx#, ny#, nz#, color as dword, zEnable)
	D3DFUNC_BLOCKSTART
		d3d_circle3d camId, r#, x#, y#, z#, nx#, ny#, nz#, color, zEnable
	D3DFUNC_BLOCKEND
	NOD3DFUNC_BLOCKSTART
		local camDistSq as float, rSq as float, aStep, x1, y1, x2, y2, oldColor as dword, rVec as vec3d, uVec as vec3d, fVec as vec3d
		
		// Up Vector = (nx#,ny#,nz#) from input parameters.
		// Fwd Vector = Up Vector x World Up (0,1,0).
		// Right Vector = Up Vector x Fwd Vector.
		
		normD# = sqrt(nx#*nx# + ny#*ny# + nz#*nz#)
		if normD# = 0 then exitfunction
		
		uVec.x = nx# / normD#
		uVec.y = ny# / normD#
		uVec.z = nz# / normD#
		
		CrossProduct(uVec, worldUp)
		Normalise(__vec3d)
		fVec = __vec3d
		
		CrossProduct(uVec, fVec)
		Normalise(__vec3d)
		rVec = __vec3d
		
		// change circle quality with camera distance.
		rSq = r#*r#
		camDistSq = (x# - camera position x(camId))^2 + (y# - camera position y(camId))^2 + (z# - camera position z(camId))^2 - rSq
		if camDistSq > rSq*14400.0 : aStep = 90 : else : if camDistSq > rSq*576.0 : aStep = 60 : else : aStep = 24 : endif : endif
		
		oldColor = get foreground color()
		ink color
		
		for a = 0 to 360 step aStep
			// position around circle, sample screen position, draw lines
			RcosA# = cos(a) * r# : RsinA# = sin(a) * r#
			_x# = x# + rVec.x * RcosA# + fVec.x * RsinA#
			_y# = y# + rVec.y * RcosA# + fVec.y * RsinA#
			_z# = z# + rVec.z * RcosA# + fVec.z * RsinA#
			position object objMarker, _x#, _y#, _z#
			x2 = object screen x(objMarker) : y2 = object screen y(objMarker)
			if object in screen(objMarker) and a <> 0 then line x1, y1, x2, y2`, color
			x1 = x2 : y1 = y2
		next a
		ink oldColor
	NOD3DFUNC_BLOCKEND
endfunction



.
Posted: 17th Sep 2014 9:47
Monocoder, I was thinking about this the other day. I can't wait to get home and give your code a crack!