2022/06/16

看板の縦横調節

イベントの看板はたいてい正方形の画面です。
でも正方形でなくてももちろんOKで 今度のイベント(16日木曜日)では縦が長い看板です。

そこでどんな縦横比の看板でも対応できるようにしました。

看板飛行船の準備は箱を編集して画像を貼ることで(Edit > Texture > Texture)できますが もし看板画面が正方形でない場合には箱の横幅も変えます。(Edit > Object > Size:X)

こうすると飛行船が飛ぶときにさげる看板の縦横比を箱と同じにします。

画面縦横の比率だけが問題なので横幅(X)さえ変えれば箱の高さ(Z)は変えなくてOK!
(…高さを変えると箱の一部が地面に埋まったり逆に浮いたりするので あれ?調整が必要?と悩むはめに…(^^;)…実際にはもちろんこの心配も不要!)

看板の縦横比を調節するスクリプト:

listen(integer ch,string na,key id,string msg)
{
  list info = llParseString2List(msg,[":"],[]);
  string command = llList2String(info,0);
  string data = llList2String(info,1);
  if (command=="XzRatio") { // 縦横比
    float xzratio = (float)data;
    vector size = getSignParams(PRIM_SIZE); // 看板のサイズ
    vector pos = getSignParams(PRIM_POS_LOCAL); // ローカル位置
    pos.z += size.z*0.5; // 看板の上端
    size.x = llSqrt(size.x*size.z*xzratio); // 画面の横幅, 面積は同じ
    size.z = size.x/xzratio; // 画面の高さ
    pos.z -= size.z*0.5; // 看板の中心
    llSetLinkPrimitiveParams(LinkSign,[PRIM_POS_LOCAL,pos,PRIM_SIZE,size]);
  }
}

箱も看板も Xが横で Zが縦です。
スクリプトはイベントが始まる飛行船を出すタイミングで箱の縦横比を調べて Rez した飛行船に伝えます。
飛行船の側ではもとからつけてる看板と面積は同じになるように縦横の長さを変えます。

さげる位置も看板のてっぺんが同じになるように Z 方向の位置を調節しています。

ページ: Airship Go Round Signboard Box: How to Use

2022/06/09

自動で飛ばす飛行船

ベネチアSIM のイベントのとき いつも宣伝看板をさげた飛行船を飛ばしていますが 何度かその時間に来れないことがあったので 事前に用意しておけばその時間に自動で飛ばせるようにしました。
やりかたは:

  1. 専用の箱をイベント会場の近くに置いて
  2. 箱に宣伝用画像を貼れば(Edit > Texture > Texture)準備完了!
    (…準備ができると写真のようにミニ飛行船が出ます)
  3. イベント開始時刻になれば飛行船が自動で飛び始め
  4. 終了時刻で飛行船も箱も消えます

開始と終了時刻はほとんど 22:00-24:00(JST) なのであらかじめ箱の説明のところに書いてあります。
もしも違う時間に開催されるならこれを書き直す(Edit > General > Description)だけでOK!

準備は超簡単なので誰でもできそうです。
イベントスタッフの人に渡しておけば当日 lovemax がいなくても飛行船が飛ばせるのでこれはサボれる!

飛行船を出したり消したりするきっかけを決めるスクリプトはこちら:

integer isNowArrive(integer time)
{ // TRUE: 指定時刻 time <= now < time+Margin に今到達した?
  integer now = getNowMin();
  if (time <= now && now < time+Margin) return TRUE;
  else return FALSE;
}
integer getNowMin()
{ // time: "2022-06-09T00:01:43..." UTC → return 541 JST(+9:00)
  string time = llGetTimestamp(); // UTC 協定世界時
  list info = llParseString2List(time,["T"],[]); // 日付と時刻を分ける
  time = llList2String(info,1); // UTC
  integer now = time2Min(time) + OffsetMin; // 分 JST 日本標準時
  if (28*60 <= now) return now - 24*60; // 28時以降は早朝 4時〜
  else return now;
}
integer time2Min(string time)
{ // time: "01:15:58" → return 75 分; 秒は無視
  list info = llParseString2List(time,[":"],[]);
  integer hour = (integer)llList2String(info,0);
  integer min = (integer)llList2String(info,1);
  if (hour<0) min *= -1; // 時間がマイナスのときは分もマイナス
  return 60 * hour + min;
}

時刻は全部分に換算して比較してます。
現在時刻は llGetTimestamp() で UTC (wikipedia 協定世界時) を調べて 時 x 60 + 分 に JST (wikipedia 日本標準時) オフセットの +9 x 60 を足します。
指定する時刻が真夜中以降の場合は 例えば正規の書き方 "00:30" ではなく "24:30" のように前日から続く深夜の書き方で前後を比べやすくしてます。
(…でも 28時以降はさすがに次の日の早朝 4時〜扱いに…)

ページ: Airship Go Round Signboard Box: How to Use

2022/04/17

超簡単パーティクル

SLでいろいろ楽しいパーティクルの視覚効果ですがスクリプトで書こうとするとなんだかとても大変そうな感じです。
設定する項目や数字がいろいろあってしかもそれぞれ意味がよくわかりません。

そこでこれ以上簡単にはできない?という究極の超簡単なパーティクルというのを考えてみました。
イベントのとき会場上空を回る看板つき飛行船に仕込んだ ぽこぽこ白いのが飛び出してふわ〜っと漂うパーティクルです。

スクリプトはこちら:

default {
  state_entry() {
    // 最初はパーティクルを出さない
    llParticleSystem([]);
    // ここで超簡単なパーティクルの設定
    Particle = [
      PSYS_PART_FLAGS, PSYS_PART_WIND_MASK // 風に漂う
      | PSYS_PART_EMISSIVE_MASK, // 自分で光る(周りの光の影響を受けない)
      PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE, // 発生方向の角度を設定
      PSYS_SRC_ANGLE_BEGIN, 0.3*PI // 角度: 0.3x180=54°
    ];
  }
  link_message(integer n,integer i,string msg,key id) {
    // リンクメッセージで開始または終了
    if ((float)msg) llSetTimerEvent(1);
    else llResetScript();
  }
  timer() { // ランダムな時間間隔でパーティクルを出したり止めたり
    llSetTimerEvent(MinTime+llFrand(MaxTime-MinTime));
    Emit = !Emit;
    if (Emit) llParticleSystem(Particle);
    else llParticleSystem([]);
  }
}

ほんの数行の簡単設定でもとりあえずパーティクルが出るのはデフォルト設定があるからですね。

超簡単では物足りない人向けには LSLポータルのページに詳しい説明がどっさり書いてあります。ただしデフォルトについては書いてないみたいです。(llParticleSystem)

ビューワーの Firestorm のメニューに Build > Object > Edit Particles というのがあって オブジェクト編集中ならそこから発生するパーティクルを編集できる画面が開きます。(つい最近友達に教えてもらいました。知らなかった〜)
この画面に最初から出ている項目や数値がデフォルトなのでしょう。
編集画面の [Copy] ボタンを押せば設定された項目とか数値がクリップボードにコピーされます。
[Inject] ボタンはその編集中のオブジェクトの中に New Particle Script というスクリプトが注入されます。

あと SLインワールドに The Particle Laboratory という場所があって そこではいろいろな展示物やサンプルスクリプトなどでパーティクルのことを詳しく教えてもらえます。
SLurl: https://maps.secondlife.com/secondlife/Teal/196/47/302

2022/03/12

イージングアニメーション

SL 内でオブジェクトを動かしたり形を変えるようなときイージングというアニメーションの定番的な方法を使うともっともらしいです。
物を動かすとき最初はゆっくりしか動かず はずみがつくと速くなって 止まるときはまたゆっくり というような感じです。
(参照: Prototype easing curves)

SL スクリプトにはそういう仕組みが用意されてないようなので作ってみました。

作り方はベジエ曲線という数学的な曲線(wikipedia Bézier curve)の描き方があって その曲線が時間経過と位置や大きさの関係を表します。
よく使われるのは3次ベジエ曲線/Cubic Bezier Curve で 4つの点 P0,P1,P2,P3 のうち P0=(0,0), P3=(1,1) は固定であとの P1と P2 の設定で 0 から 1 までの時間と変化の関係を決めます。
計算式は B = (1-t)3P0+3(1-t)2tP1+3(1-t)t2P2+t3P3
スクリプトで書けば

vector cubicBezier(float t) {
  float t1 = 1.0 - t;
  return t1*t1*t1*P0 + 3.0*t1*t1*t*P1 + 3.0*t1*t*t*P2 + t*t*t*P3;
}

ところで曲線を描くのに時間要素 t を変化させますが これは結果の曲線(x,y)の x: アニメーションの経過時間とは微妙に違います。
そこで描いた曲線 (x,y) からアニメーションの経過時間 t に等しい x に対応する変化量 y の値を 計算するには 曲線上の前後の点から補間します。

float easing(integer i) {
  if (i<=0) return 0.0; // 最初
  else if (N<=i) return 1.0; // 最後
  float t = (float)i/(float)N; // 経過時間→曲線を描くときにも使う
  vector b = cubicBezier(t); // 描いた曲線上の点
  if (t<=b.x) --i; // 1つ前
  else ++i; // 1つ後ろ
  float t0 = (float)i/(float)N;
  vector b0 = cubicBezier(t0); // 曲線上の隣の点
  float r = (b.x-t)/(b.x-b0.x); // 時間の差と隣までの差の比率
  vector br = b - r*(b-b0); // 補間した経過時間の曲線上の点
  return br.y;
}

補間計算のための数値は前後1つ隣しか見てませんが 同じ数で時間を刻めば描いた曲線の点も経過時間に近くて 変化が激しくなければ誤差もきっと小さいでしょう。

このアニメーションは lovemax ベネチア店の最上階 お城のデモを見に行く箱に仕込みました。
ほかにももう一つ わざとわかりにくくした場所にも仕掛けてますが 怪しいところを探せばきっと…。

2022/03/06

螺旋階段は時計回り?

 

西洋のお城の塔にある螺旋階段は登るとき時計回りが基本だそうです。
それは戦争のとき 攻め登る兵士は右手に持った剣を振るのに柱がじゃまになるけど 上から守る側の兵士は右手が自由に使えるので有利だから …。

その話をディスカバリーチャンネルか何かで見て 売ってるお城(参照: お城と岩のセット販売)を確認したら何も考えずに作ってたのでやっぱり逆回りでした。(><)

作ったときは半分に削ったトーラス(ドーナツ状のプリム)を引き伸ばして螺旋階段に見立ててましたが最近の何でもメッシュの世の中に迎合してついに階段だけメッシュ部品と入れ替えました。

すると部品は 1プリムでランドインパクトが 2 なのに大きさを変えるとランドインパクトが 3 になったりほかのプリムとリンクすると全体のランドインパクトがもっと増えるとか不思議なことが起こります。
もとのお城はプリムだけでできていて19プリム=19ランドインパクトですが 階段をメッシュ部品に入れ替えたら同じプリム数でもランドインパクトが 34になるのです!
…メッシュは謎です…。

ところで螺旋階段は本当に時計回りが多いか画像検索してみたら…逆回りもたくさんあります??
しかもお城の攻防で逆の説も出てきました:
螺旋階段のステップは扇型で柱側が狭く壁側が広いので兵士は足場を確保するため壁側に寄る そのとき攻め登る側は反時計回りの階段のほうが右手が壁に近くて自由が利かずに不利…。
…うーんどっちが正しいのか どっちも単なる都市伝説なのか…。

ちなみに lovemax の製品 MyCastle では いちいち階段を登らなくても柱にタッチ→メニューで行き先を選んでジャンプ で行けるので どっち回りか気にしなくてOK!

MyCastle はベネチアのお店の最上階から箱に乗って上空でデモを見ることができます。
あと 上空に行くと作りかけのベネチアの街も見ることができてベネチア SIM がどうやって作られているか雰囲気がなんとなくわかります。



2022/02/23

びっくり?宝箱

lovemax ワゴンの3番目アイテム: びっくり?宝箱 はもとの宝箱(参照: 金貨ぎっしり宝箱)に びっくり?機能を追加してさらに価格を 3分の1(!)まで下げた超お得な商品です。ベネチアカーニバル特設ワゴンセール限定。
…といってももとの価格はそもそもネタでつけた値段だったので その 3分の1もまたネタ価格。

ネタの話が出たので追加ネタを2つ。
ネタバレになって面白みが減ってはいけないので お買い上げしていただいた またはワゴンで確認していただいたお客さんのためだけに書くと:


追加ネタ1: ゼッキーニ金貨

ベネチア共和国で1284年からナポレオンに支配される1797年まで500年以上同じデザインで発行されていた金貨です。

写真は最後から2番目のドージェ(総督)パオロレニエの治世(1779-89年)中に作られた50ゼッキーニ金貨: 直径76mm 重さ192.5g
図柄はドージェが聖マルコに跪いています。(wikipedia ゼッキーノ/Sequin)


追加ネタ2: エトナ火山

イタリア南部シチリア島にある活火山です。

ヨーロッパ最大の活火山で 2018年の標高は3,326mですが活発な活動で高さはしょっちゅう変わるそうです。
アルプスを除いたイタリアでは最も高い山で2番目のヴェスヴィオ山の3倍近くもあります。
50万年前から頻繁に噴火を繰り返して 最近でもついおととい 2022年2月21日噴火があったようです。(wikipedia エトナ火山)

2022/02/21

特設ワゴンセール

ベネチア SIM のサンマルコ広場にはたくさんのセールスワゴンが並んでいます。
lovemax のワゴンで売ってるのは3つ:
  1. 肩乗り飛行船 (無料)
  2. 画像切り替え絵画 (L$ 55)
  3. びっくり?宝箱 (L$ 340)
2. 画像切り替え絵画 はベネチアの景観をたくさん描いた18世紀の画家カナレット(wikipedia カナレット/Giovanni Antonio Canal/1697-1768)の4枚の絵と昔のベネチアの地図が一定時間で切り替わります。

切り替え時間の設定は中のスクリプトを編集します。秒数などで指定できるほか h:時間, d:日, w:週, m:月, s:季節, y:年のように文字も使えます。

切り替えを日付から調べるスクリプト:

integer getCycleDate()
{ // return 年:2022-, 季節:1-4, 月:1-12, 0:それ以外
  if (Cycle=="") return 0;
  integer i = llSubStringIndex("msy",Cycle);
  if (i<0) return 0;
  string date = llGetDate(); // 日付: YYYY-MM-DD
  list info = llParseString2List(date,["-"],[]); // 年,月,日に分解
  if (i==2) return (integer)llList2String(info,0); // 年
  else {
    integer month = (integer)llList2String(info,1);
    if (i==0) return month; // 1-12月
    if (month==12) month = 0; // 12月を0に, 1-11月はそのまま
    return 1 + month / 3; // 季節; 1:冬-4:秋
  }
}

月,季節,年は毎日チェックしてそれ以外は指定した秒数または時間:h→60x60秒, 日:d→24x60x60秒, 週:w→7x24x60x60秒で切り替えます:

timer() {
  integer date = getCycleDate();
  if (date) { // 月,季節,年
    if (Date==date) return; // 前と同じなら何もしない
    else Date = date; // 違ってたら更新
  }
  setPicture(); // 絵を切り替える
}