(oops - the 'post too long' page put an extra [DBP] in the thread subject, didn't catch it)
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