While pondering my cascaded shadow mapping issues as of late, I happened to realize a way that works for implementing the same functionality as provided by the built-in DBPro function
pick object.
There are some advantages to this;

It supports any kind of camera projection, such as orthographic. (The built-in function does not).

It is written in pure DBPro (or well, it uses IanM's
M1U, but what doesn't these days?). As such you can extend / change it to fit your requirements. One of the most important things this allows you to do is pick objects from a list instead of having to use a contiguous range of object id's (ie. 100 - 499) like with the built-in function.

Potential to be more efficient than the built-in function. Using the built-in
intersect object function, as is done in the demonstration below, this runs about 20% slower than the standard
pick object function. However, using a more efficient raycasting function, like can be found in most third party physics / collision dll's, you can make this run significantly faster than the standard implementation, which is obviously a big plus when picking a large amount of objects in realtime. For example, using the raycasting function from Paul Johnston's ("Sparky"'s)
free collision library, this function only requires ~19.7% of the executing time of the built-in version.
The actual function:
rem *********************************************************
rem * Works similar to the built-in PICK OBJECT function. *
rem * *
rem * Works by casting a ray from the specified screen *
rem * position, unprojected into world space from the near *
rem * to the far plane of the camera and checking against *
rem * intersections with objects along the way. *
rem * Unlike the built-in function, this will work with *
rem * orthographic (and presumably any other kind of) *
rem * camera projections. *
rem * It is also easy to modify this function to select *
rem * object id's from a custom list instead of having to *
rem * provide a contiguous stream of id's to check against. *
rem * Replacing the INTERSECT OBJECT call with a third *
rem * party function of the same persuasion can also make *
rem * this function several times faster than the built-in *
rem * version. *
rem * *
rem * The final parameter «vecPick» is a VECTOR4 id. *
rem * This vector will be filled out with the «world» *
rem * coordinates of the collision between the projected *
rem * ray and the closest object if there was any such *
rem * collision. This differs from the built-in PICK OBJECT *
rem * function which returns «view space» *
rem * (relative-to-camera) coordinates. through the *
rem * GET PICK VECTOR X/Y/Z functions. *
rem * The W component of «vecPick» will be set to the *
rem * distance from the near plane to the collision point. *
rem * This parameter can be set to 0 if this information is *
rem * not required to be returned. *
rem * The function will furthermore return the ID of the *
rem * picked object, or 0 if no object was found; just like *
rem * the built-in function works. *
rem * *
rem * Written by Joel Sjöqvist ("Rudolpho"), 2013-11-03. *
rem *********************************************************
function PickObject(x as dword, y as dword, objStart as dword, objEnd as dword, vecPick as dword)
matView = new matrix4()
matProj = new matrix4()
matInvViewProj = new matrix4()
view matrix4 matView
projection matrix4 matProj
multiply matrix4 matInvViewProj, matView, matProj
determinant# = inverse matrix4(matInvViewProj, matInvViewProj)
rem Determine clip space position from the input screen space coordinates
clipX# = (2 * (x / (1.0 * screen width()))) - 1.0
clipY# = (2 * (1.0 - (y / (1.0 * screen height())))) - 1.0
rem Convert clip coords to world space
vecFromPos = new vector3(clipX#, clipY#, 0.0) ` Pick from the near plane
vecToPos = new vector3(clipX#, clipY#, 1.0) ` And to the far plane; further distances are irrelevant
transform coords vector3 vecFromPos, vecFromPos, matInvViewProj
transform coords vector3 vecToPos, vecToPos, matInvViewProj
closestDist# = 999999.9
closestObjId = 0
for obj = objStart to objEnd
dist# = intersect object(obj, x vector3(vecFromPos), y vector3(vecFromPos), z vector3(vecFromPos), x vector3(vecToPos), y vector3(vecToPos), z vector3(vecToPos))
rem Hit?
if dist# > 0.0
if dist# < closestDist#
closestObjId = obj
closestDist# = dist#
endif
endif
next obj
rem Did we find any target object under the mouse? If we provided a pick vector we can fill that one out then.
if closestObjId > 0 and vecPick > 0
rem Get pick direction vector
vecTmp = new vector3()
subtract vector3 vecTmp, vecToPos, vecFromPos
normalize vector3 vecTmp, vecTmp
rem The hit coordinate is the from vector + (distance * direction vector)
multiply vector3 vecTmp, closestDist#
add vector3 vecTmp, vecFromPos, vecTmp
rem We may also want to return the pick distance; we can achieve this by having vecPick be a vector4 and putting it in the W component.
rem NOTE: This means that we should not treat the pickVector as a position and use it in 3d math right away. Extract the X, Y and Z components
rem first and put them in a new vector3 / vector4 / what-have-you for that.
set vector4 vecPick, x vector3(vecTmp), y vector3(vecTmp), z vector3(vecTmp), closestDist#
delete vector3 vecTmp
endif
delete vector3 vecToPos
delete vector3 vecFromPos
delete matrix4 matInvViewProj
delete matrix4 matProj
delete matrix4 matView
endfunction closestObjId
A small, ugly demonstration program (no external media required, just copy, paste and compile):
rem #############################################
rem # Demonstration of custom object picking #
rem # functionality, supporting custom camera #
rem # projection types and also readily #
rem # extendible to pick object id's from a #
rem # custom list instead of a fixed id range #
rem # like the built-in function uses. #
rem # Using third party raycasting plugins it #
rem # is also possible to make this #
rem # implementation run significantly faster #
rem # than the built-in one. #
rem # #
rem # By Joel Sjöqvist ("Rudolpho"), 2013-11-03 #
rem #############################################
rem Constants
#constant true 1
#constant false 0
rem Setup engine
set display mode 1600, 900, 32
set window size 1600, 900
set window position (desktop width() - screen width()) / 2, (desktop height() - screen height()) / 2
sync on
sync rate 0
backdrop on
color backdrop 0xff808080
autocam off
rem Ensure equal illumination from all directions (except from below since we'll never see the scene from there anyway)
make light 1
make light 2
make light 3
make light 4
set directional light 0, 1, 0, 0
set directional light 1, -1, 0, 0
set directional light 2, 0, 0, -1
set directional light 3, 0, 0, 1
set directional light 4, 0, -1, 0
ink 0xffffc000, 0
rem Setup orthographic and perspective projection matrices
matProjOrtho = new matrix4()
matProjPerspective = new matrix4()
build ortho lhmatrix4 matProjOrtho, camera aspect() * 48, 48, 1, 72
projection matrix4 matProjPerspective ` Just use the default one here
rem Create simple scene made up of stacked cubes
dim Block(32 * 32 * 6) as dword
nextObjId = 1
for x = 0 to 31
for y = 0 to 31
for z = 0 to 5
rem Should we spawn a block here?
if rnd(z) + 1 >= z and IsValidBlockPosition(x, y, z)
Block((z * 1024) + (y * 32) + x) = nextObjId
make object cube nextObjId, 1
position object nextObjId, x - 16, z, y - 16
rem Colour block based on it's height
lum = 63 + (25.6 * z)
color object nextObjId, 0xff << 24 || lum << 16 || lum << 8 || lum
inc nextObjId
endif
next z
next y
next x
rem Create a pick position indicator object to demonstrate that we can return the point of collision between the object and the ray from the screen position
pickIndicator = nextObjId
make object sphere pickIndicator, 0.4
color object pickIndicator, 0xffc0ff00
rem This will work like the built in GET PICK VECTOR X/Y/Z commands. The pick distance is stored in the W component.
pickVector = new vector4()
rem Allow rotating the camera between even 90 degree points of view
cameraCurrentAngle# = 0.0
cameraTargetAngle# = 0.0
rem Store last state of the space key in order to use it as a toggle button
bIsSpacekeyDown = false
bIsOrthographicMode = false
tick = timer() - 1
while not escapekey()
rem Update timer
lastTick = tick
tick = timer()
timeForce# = (tick - lastTick) * 0.001
rem Pick objects by putting the mouse cursor over them and clicking the left mouse button
if mouseClick() && 1
obj = PickObject(mouseX(), mouseY(), 1, nextObjId - 1, pickVector)
if obj > 0
color object obj, 0xffff0000
position object pickIndicator, x vector4(pickVector), y vector4(pickVector), z vector4(pickVector)
endif
endif
rem Toggle projection type with the space key
bSpacekey = spacekey()
if bSpacekey <> bIsSpacekeyDown
if bSpacekey
if bIsOrthoGraphicMode
apply projection matrix4 matProjPerspective
else
apply projection matrix4 matProjOrtho
endif
bIsOrthographicMode = 1 - bIsOrthographicMode
endif
bIsSpaceKeyDown = bSpacekey
endif
rem Rotate the camera in 90-degree steps around the scene
cameraCurrentAngle# = curveAngle(cameraTargetAngle#, cameraCurrentAngle#, 10000 * timeForce#)
position camera cos(cameraCurrentAngle#) * 48, 10, sin(cameraCurrentAngle#) * 48
point camera 0, 0, 0
rem Can rotate by pressing the left / right arrow keys when we're close enough to an angle divisable by 90 degrees
angleDif# = abs(cameraTargetAngle# - cameraCurrentAngle#)
if angleDif# < 10.0 or angleDif# > 350.0
cameraTargetAngle# = cameraTargetAngle# + (90 * (rightkey() - leftkey()))
endif
rem Ensure that we keep within the 0 .. 360° range
cameraTargetAngle# = wrapvalue(cameraTargetAngle#)
cameraCurrentAngle# = wrapvalue(cameraCurrentAngle#)
rem Text output
if bIsOrthographicMode
text 0, 0, "Orthographic projection"
else
text 0, 0, "Perspective projection"
endif
center text screen width() / 2, screen height() - 60, "Click on a block to pick it and colour it red."
center text screen width() / 2, screen height() - 40, "Use the left / right arrow keys to rotate the camera."
center text screen width() / 2, screen height() - 20, "Press [space] to toggle between perspective and orthographic projection."
sync
endwhile
rem Returns true if the given block position is valid for spawning a new block, or false otherwise
function IsValidBlockPosition(x as dword, y as dword, z as dword)
rem Already a block here?
if Block((z * 1024) + (y * 32) + x) > 0 then exitfunction false
rem In the bottom row and no block here?
if z = 0 then exitfunction true
rem Is there a block in the row beneath this one?
if Block(((z - 1) * 1024) + (y * 32) + x) > 0 then exitfunction true
rem Otherwise the position isn't available
endfunction false
rem Works similar to the built-in PICK OBJECT function.
rem The main difference is that this one works with any projection matrix, including orthographic ones.
rem Since it is a pure DBP function it can also easily be tweaked to select object ID's to pick from a custom
rem list instead of using a continuous range.
rem Comparing this with the built-in PICK OBJECT function, this implementation is ~21% slower.
rem It might be possible to speed it up by excluding impossible object early (bounding box / sphere checks etc.).
rem Also, using another implementation of the INTERSECT OBJECT call can increase speeds significantly. For example
rem using sc_intersectObject instead (Paul Johnston's ("Sparky") DBP collision DLL v2.05) makes this implementation run
rem 5x faster(!) than the built-in PICK OBJECT function (more precisely at 19.7% of its speed). Note that I only tested it
rem using box collisions however; in theory it should be even more efficient for complex objects.
function PickObject(x as dword, y as dword, objStart as dword, objEnd as dword, vecPick as dword)
matView = new matrix4()
matProj = new matrix4()
matInvViewProj = new matrix4()
view matrix4 matView
projection matrix4 matProj
multiply matrix4 matInvViewProj, matView, matProj
determinant# = inverse matrix4(matInvViewProj, matInvViewProj)
rem Determine clip space position from the input screen space coordinates
clipX# = (2 * (x / (1.0 * screen width()))) - 1.0
clipY# = (2 * (1.0 - (y / (1.0 * screen height())))) - 1.0
rem Convert clip coords to world space
vecFromPos = new vector3(clipX#, clipY#, 0.0) ` Pick from the near plane
vecToPos = new vector3(clipX#, clipY#, 1.0) ` And to the far plane; further distances are irrelevant
transform coords vector3 vecFromPos, vecFromPos, matInvViewProj
transform coords vector3 vecToPos, vecToPos, matInvViewProj
closestDist# = 999999.9
closestObjId = 0
for obj = objStart to objEnd
dist# = intersect object(obj, x vector3(vecFromPos), y vector3(vecFromPos), z vector3(vecFromPos), x vector3(vecToPos), y vector3(vecToPos), z vector3(vecToPos))
rem Hit?
if dist# > 0.0
if dist# < closestDist#
closestObjId = obj
closestDist# = dist#
endif
endif
next obj
rem Did we find any target object under the mouse? If we provided a pick vector we can fill that one out then.
if closestObjId > 0 and vecPick > 0
rem Get pick direction vector
vecTmp = new vector3()
subtract vector3 vecTmp, vecToPos, vecFromPos
normalize vector3 vecTmp, vecTmp
rem The hit coordinate is the from vector + (distance * direction vector)
multiply vector3 vecTmp, closestDist#
add vector3 vecTmp, vecFromPos, vecTmp
rem We may also want to return the pick distance; we can achieve this by having vecPick be a vector4 and putting it in the W component.
rem NOTE: This means that we should not treat the pickVector as a position and use it in 3d math right away. Extract the X, Y and Z components
rem first and put them in a new vector3 / vector4 / what-have-you for that.
set vector4 vecPick, x vector3(vecTmp), y vector3(vecTmp), z vector3(vecTmp), closestDist#
delete vector3 vecTmp
endif
delete vector3 vecToPos
delete vector3 vecFromPos
delete matrix4 matInvViewProj
delete matrix4 matProj
delete matrix4 matView
endfunction closestObjId
"Why do programmers get Halloween and Christmas mixed up?" Because Oct(31) = Dec(25)