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:
+ Code Snippetrem *********************************************************
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):
+ Code Snippetrem #############################################
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