ロックオンできるHUDといっても、よくわからないと思うので、まずは動画をみてください。
赤い箱は土地にRezされたオブジェクトで、青い四角はHUDです。赤い箱にタッチすると箱が自分の現在の位置をシャウトするようになってて、それをHUD側がListenして箱が表示されている座標へ移動するようになってます。3Dシューティングなんかで敵の位置へカーソルが移動してロックオン状態になったりしますが、まさにそんな感じのものをつくりました。
使用している技術は透視投影変換で、これは3D上の世界を2次元の画面へマッピングするための手法です。ただ、これを説明するのは、ものすごく大変というか自分も完全には理解してないので、とりあえず赤い箱の位置を渡せばHUDをロックオンする位置に移動させる関数をいきなりお見せします。
lockOn(vector pos)
{
float fov = 1.0 / llTan(60.0 * 0.5 * PI / 180.0);
vector cameraPos = llGetCameraPos();
rotation cameraRot = llGetCameraRot();
float dist = llVecDist(pos, cameraPos);
vector objVec = pos - cameraPos;
vector xVec = <1.0, 0.0, 0.0> * cameraRot;
vector yVec = <0.0, 1.0, 0.0> * cameraRot;
vector zVec = <0.0, 0.0, 1.0> * cameraRot;
rotation xRot = llRotBetween(objVec, xVec);
rotation yRot = llRotBetween(objVec, yVec);
rotation zRot = llRotBetween(objVec, zVec);
float xDist = dist * llCos(llRot2Angle(xRot));
float yDist = dist * llCos(llRot2Angle(yRot));
float zDist = dist * llCos(llRot2Angle(zRot));
float posY = yDist / xDist * fov / 2.0;
float posZ = zDist / xDist * fov / 2.0;
vector pos = <0.0, posY, posZ>;
llSetPrimitiveParams([PRIM_POSITION, pos]);
}
ゲームプログラミングとかしてる人じゃないと分かりませんよね。3Dの世界を2次元にマッピングする場合、遠くにあるものほど真ん中へ寄せて表示しないとそれらしくならないので、そういう処理をしてると思ってください。
図にするとこんな感じかな。
この技術はカソウセカイカメラでも使われています。まあちょっとしたバグがあって、コメントは正確な位置から少しズレるんですが・・・。コメントを空間に表示するとき位置をどう決めればいいのかわからなくて、試行錯誤しててこの方法にたどりついた感じです。
応用範囲は広くて、3Dシューティングゲームを作ったりできますし、なにかモノを指し示すガイドとしても使えるとおもいます。特に後者は、情報が整理されておらずカオスな仮想世界で何か探してもらうときに、かなり役に立つんじゃないでしょうか。
最後に実際に動作確認したスクリプトを載せておきます( ̄∇  ̄ )
赤い箱のスクリプト
integer CHANNEL = -84527;
default
{
touch_start(integer total_number)
{
llShout(CHANNEL, (string)llGetPos());
}
}
青い四角のスクリプト
float ANGLE = 60.0;
integer CHANNEL = -84527;
lockOn(vector pos)
{
float fov = 1.0 / llTan(ANGLE * 0.5 * PI / 180.0);
vector cameraPos = llGetCameraPos();
rotation cameraRot = llGetCameraRot();
float dist = llVecDist(pos, cameraPos);
vector objVec = pos - cameraPos;
vector xVec = <1.0, 0.0, 0.0> * cameraRot;
vector yVec = <0.0, 1.0, 0.0> * cameraRot;
vector zVec = <0.0, 0.0, 1.0> * cameraRot;
rotation xRot = llRotBetween(objVec, xVec);
rotation yRot = llRotBetween(objVec, yVec);
rotation zRot = llRotBetween(objVec, zVec);
float xDist = dist * llCos(llRot2Angle(xRot));
float yDist = dist * llCos(llRot2Angle(yRot));
float zDist = dist * llCos(llRot2Angle(zRot));
float posY = yDist / xDist * fov / 2.0;
float posZ = zDist / xDist * fov / 2.0;
vector pos = <0.0, posY, posZ>;
llSetPrimitiveParams([PRIM_POSITION, pos]);
}
default
{
state_entry()
{
llListen(CHANNEL, "", NULL_KEY, "");
if(llGetAttached() >= ATTACH_HUD_CENTER_2 && llGetAttached() <= ATTACH_HUD_BOTTOM_RIGHT)
{
llRequestPermissions(llGetOwner(), PERMISSION_TRACK_CAMERA);
}
}
on_rez(integer start_param)
{
llResetScript();
}
listen(integer channel, string name, key id, string message)
{
lockOn((vector)message);
}
run_time_permissions(integer perm) {
if(perm & PERMISSION_TRACK_CAMERA) {
}
}
}