最後の2つの記事では、プロセスの基本に精通し、ARを操作するためにケースを分解し始めました。 最新の状態に保つためにお読みください。
パートI:オブジェクト認識の基本パートII:オブジェクトを認識して3Dモデルを表示する方法最後の3番目の部分(juhu!)を提示できることを嬉しく思います。この部分では、エンジンの機能をさらに詳しく説明し、次のことを確認します。
- ARの3Dオブジェクトをアニメーション化する方法。 オブジェクトをクリックしてテキストを表示します。
- 特定の座標で3DオブジェクトをPOI(関心のあるポイント)として表示する方法。
- 自分に関連する3Dオブジェクトを表示する方法。
3Dモデルとアニメーションについて一言
認識用の画像をダウンロードするためのアプリケーションはすでに見ました。 そのため、Wikitudeにはモデルとアニメーションを読み込むためのエディターがあります。 このツールは、Wikitude 3Dエンコーダーと呼ばれます。
これを使用すると、* .fbxや* .daeなどのファイルからコンテンツをダウンロードし、その後* .wt3対応のWikitude形式でエクスポートできます。
Wikitude 3Dエンコーダーの3Dモデルすでに分解した3Dモデルの例、アニメーションについて話しましょう。 アニメーションは特別なソフトウェアからエクスポートすることも、自分でプログラムすることもできます。 最初のケースでは、Autodesk mayaがこれに使用されました。
エディターでの直接的なアニメーションの例。 利用可能なアニメーションはすべて右側に表示されていることに注意してください。少しのコードは決して痛いことはありません:
| // ModelAnimation |
| this.animationDoorL = new AR.ModelAnimation(this.model, "DoorOpenL_animation"); |
| this.animationDoorR = new AR.ModelAnimation(this.model, "DoorOpenR_animation"); |
| this.animationEngine= new AR.ModelAnimation(this.model, "EngineWindow_animation"); |
| this.animationHood = new AR.ModelAnimation(this.model, "Trunkopen_animation"); |
| |
| // |
| this.model.onClick = function( drawable, model_part ) { |
| switch(model_part) |
| { |
| case 'WindFL': |
| case 'DoorL[0]': |
| case 'DoorL[1]': |
| case 'DoorL[2]': |
| case 'DoorL[3]': |
| World.animationDoorL.start(); |
| break; |
| |
| case 'WindFR': |
| case 'DoorR[0]': |
| case 'DoorR[1]': |
| case 'DoorR[2]': |
| case 'DoorR[3]': |
| World.animationDoorR.start(); |
| break; |
| |
| case 'Rear[0]': |
| case 'Rear[1]': |
| case 'WindR1[0]': |
| case 'WindR1[1]': |
| World.animationEngine.start(); |
| break; |
| |
| case 'Hood': |
| World.animationHood.start(); |
| break; |
| } |
| } |
したがって、アニメーションが開始されます。 元のアニメーション自体は、* .wt3の形式のファイルに埋め込まれています
タスクを多様化します。アニメーションをプログラムします。 画像を認識するときに、モデルをアニメーションで表示してみましょう。
| createAppearingAnimation: function createAppearingAnimationFn(model, scale) { |
| /** |
| * AR.PropertyAnimation 0 . |
| * ‘easing curve’ |
| * |
| * . |
| */ |
| var sx = new AR.PropertyAnimation(model, "scale.x", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| var sy = new AR.PropertyAnimation(model, "scale.y", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| var sz = new AR.PropertyAnimation(model, "scale.z", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| |
| return new AR.AnimationGroup(AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL, [sx, sy, sz]); |
| }, |
| |
| // |
| this.appearingAnimation = this.createAppearingAnimation(this.model, 0.045); |
| |
| ... |
| |
| // |
| loadingStep: function loadingStepFn() { |
| if (World.resourcesLoaded && World.model.isLoaded()) { |
| |
| if ( World.trackableVisible && !World.appearingAnimation.isRunning() ) { |
| World.appearingAnimation.start(); |
| } |
| } |
| }, |
トラッカーの初期化関数をわずかに変更することは残っており、次の例を取得します。
| var World = { |
| loaded: false, |
| trackableVisible: false, |
| resourcesLoaded: false, |
| |
| init: function initFn() { |
| this.createOverlays(); |
| }, |
| |
| createOverlays: function createOverlaysFn() { |
| |
| this.targetCollectionResource = new AR.TargetCollectionResource("assets/tracker.wtc", { |
| onLoaded: function () { |
| World.resourcesLoaded = true; |
| this.loadingStep; |
| }, |
| onError: function(errorMessage) { |
| alert(errorMessage); |
| } |
| }); |
| |
| this.tracker = new AR.ImageTracker(this.targetCollectionResource, { |
| onTargetsLoaded: this.loadingStep, |
| onError: function(errorMessage) { |
| alert(errorMessage); |
| } |
| }); |
| |
| this.model = new AR.Model("assets/car_animated.wt3", { |
| onLoaded: this.loadingStep, |
| scale: { |
| x: 0, |
| y: 0, |
| z: 0 |
| }, |
| translate: { |
| x: 0.0, |
| y: 0.05, |
| z: 0.0 |
| }, |
| rotate: { |
| z: -25 |
| } |
| } ); |
| |
| this.animationDoorL = new AR.ModelAnimation(this.model, "DoorOpenL_animation"); |
| this.animationDoorR = new AR.ModelAnimation(this.model, "DoorOpenR_animation"); |
| this.animationEngine= new AR.ModelAnimation(this.model, "EngineWindow_animation"); |
| this.animationHood = new AR.ModelAnimation(this.model, "Trunkopen_animation"); |
| |
| this.model.onClick = function( drawable, model_part ) { |
| switch(model_part) |
| { |
| case 'WindFL': |
| case 'DoorL[0]': |
| case 'DoorL[1]': |
| case 'DoorL[2]': |
| case 'DoorL[3]': |
| World.animationDoorL.start(); |
| break; |
| |
| case 'WindFR': |
| case 'DoorR[0]': |
| case 'DoorR[1]': |
| case 'DoorR[2]': |
| case 'DoorR[3]': |
| World.animationDoorR.start(); |
| break; |
| |
| case 'Rear[0]': |
| case 'Rear[1]': |
| case 'WindR1[0]': |
| case 'WindR1[1]': |
| World.animationEngine.start(); |
| break; |
| |
| case 'Hood': |
| World.animationHood.start(); |
| break; |
| } |
| } |
| this.appearingAnimation = this.createAppearingAnimation(this.model, 0.045); |
| |
| var trackable = new AR.ImageTrackable(this.tracker, "*", { |
| drawables: { |
| cam: [this.model] |
| }, |
| onImageRecognized: this.appear, |
| onImageLost: this.disappear, |
| onError: function(errorMessage) { |
| alert(errorMessage); |
| } |
| }); |
| }, |
| |
| removeLoadingBar: function() { |
| if (!World.loaded) { |
| var e = document.getElementById('loadingMessage'); |
| e.parentElement.removeChild(e); |
| World.loaded = true; |
| } |
| }, |
| |
| loadingStep: function loadingStepFn() { |
| if (World.resourcesLoaded && World.model.isLoaded()) { |
| |
| if ( World.trackableVisible && !World.appearingAnimation.isRunning() ) { |
| World.appearingAnimation.start(); |
| } |
| |
| var cssDivLeft = " style='display: table-cell;vertical-align: middle; text-align: right; width: 50%; padding-right: 15px;'"; |
| var cssDivRight = " style='display: table-cell;vertical-align: middle; text-align: left;'"; |
| document.getElementById('loadingMessage').innerHTML = |
| "<div" + cssDivLeft + ">Scan CarAd Tracker Image:</div>" + |
| "<div" + cssDivRight + "><img src='assets/car.png'></img></div>"; |
| } |
| }, |
| |
| createAppearingAnimation: function createAppearingAnimationFn(model, scale) { |
| var sx = new AR.PropertyAnimation(model, "scale.x", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| var sy = new AR.PropertyAnimation(model, "scale.y", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| var sz = new AR.PropertyAnimation(model, "scale.z", 0, scale, 1500, { |
| type: AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC |
| }); |
| |
| return new AR.AnimationGroup(AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL, [sx, sy, sz]); |
| }, |
| |
| appear: function appearFn() { |
| World.removeLoadingBar(); |
| World.trackableVisible = true; |
| if ( World.loaded ) { |
| // Resets the properties to the initial values. |
| World.resetModel(); |
| World.appearingAnimation.start(); |
| } |
| }, |
| disappear: function disappearFn() { |
| World.trackableVisible = false; |
| }, |
| |
| resetModel: function resetModelFn() { |
| World.model.rotate.z = -25; |
| }, |
| |
| }; |
| |
| World.init(); |
アニメーションへの2つのアプローチを検討しました。
-以前に作成し、* .wt3形式のファイルに入れます
-手動でプログラム
碑文を表示する方法
最も一般的なケースは、テキストを表示することです。 とても簡単です。 だから:
| modelAsset = new AR.Model("assets/car.wt3", { |
| onLoaded: this.onModelLoaded, |
| onClick: this.onModelClick, |
| scale: { |
| x: 0.07, |
| y: 0.07, |
| z: 0.07 |
| }, |
| |
| onModelLoaded: function onModelLoadedFn() { |
| var rotateAnimation = new AR.PropertyAnimation(modelAsset, 'rotate.heading', 0, 360, 1500); |
| rotateAnimation.start(-1); |
| }, |
| |
| onModelClick: function onModelClickFn() { |
| var e = document.getElementById('loadingMessage'); |
| e.innerHTML = " 10 !"; |
| }, |
| |
| // index.html |
| <div> |
| <div class="loadingMessage" id="loadingMessage">Loading ...</div> |
| </div> |
私たちが得たものを見てみましょう:
- テキスト用のウィンドウが作成されます。
- 3Dモデルが表示されます。
- ロード時に、回転アニメーションが割り当てられます。
- モデルをクリックすると、ウィンドウに碑文が表示されます。
ご覧のとおり、ダウンロード可能なモデルがあります。 ロードされると、モデルがその軸を中心に回転するアニメーションが割り当てられます。
このモデルをクリックすると、ウィンドウに碑文が表示されます。 ウィンドウにはアニメーションボタンもあります。 オブジェクトが認識され、独自の方法でマシンをアニメーション化する場合にのみ表示されます。
ジオロケーションとPOI
POI-興味のあるポイント、地図上のピンでマークされたオブジェクト。
私たちの場合、POIは私たちから遠すぎるオブジェクトを意味します。 私たちが半径100メートル以内で彼と一緒にいるとき、それは私たちにとって世界のポイントではなくなり、ext内のモデルに変換されます。 現実と私たちは彼と対話する機会を得ます。
問題が発生します。なぜモデルをすぐに使用できないのですか?
ポケモンゴーゲームで見たように、ある時点でポケモンが目の前に現れます。 それらは地理的位置に結び付けられておらず、あなたの地理的位置に結び付けられており、あなたはそれらの宇宙の中心(ポイント0.0.0)であり、そこから参照が行われます。
これはいくつかのポイントによるもので、最も明白なのはGPSセンサーです。これにより、オブジェクトはgeoで示されます。 彼は50メートルの往復を投げます。 これは、マップの最初に家の片側から来て、次に突然反対側から来たときに特に顕著でした。
2番目のニュアンスは、POIを2D画像の形式で表示し、そのサイズがポイントまでの距離に依存しないことです。 それ以外の場合、3Dを使用する場合、寸法を計算する必要があり、寸法が近づくと動的に変更する必要があります。
まず、地理追跡センサーを起動します。
| // location updates, fired every time you call architectView.setLocation() in native environment |
| locationChanged: function locationChangedFn(lat, lon, alt, acc) { |
| |
| if (!World.initiallyLoadedData) { |
| |
| var indicatorImage = new AR.ImageResource("assets/indi.png"); |
| World.indicatorDrawable = new AR.ImageDrawable(indicatorImage, 0.1, { |
| verticalAnchor: AR.CONST.VERTICAL_ANCHOR.TOP |
| }); |
| |
| World.targetLocation = new AR.GeoLocation(59.000573, 30.334724, AR.CONST.UNKNOWN_ALTITUDE); |
| |
| World.loadPoisFromJsonData(); |
| World.createModelAtLocation(); |
| World.initiallyLoadedData = true; |
| } |
| |
| // store user's current location in World.userLocation, so you always know where user is |
| World.userLocation = { |
| 'latitude': lat, |
| 'longitude': lon, |
| 'altitude': alt, |
| 'accuracy': acc |
| }; |
| |
| if (World.targetLocation) |
| { |
| World.stateOnDistance(); |
| |
| var latDirection = World.targetLocation.latitude - World.userLocation.latitude; |
| var lonDirection = World.targetLocation.longitude - World.userLocation.longitude; |
| } |
| }, |
| |
| World.init(); |
| AR.context.onLocationChanged = World.locationChanged; |
次に、いくつかのことを行う必要があります。
-興味のあるポイントを追加します。 loadPoisFromJsonDataメソッドでやってみましょう
-3Dモデルを追加します。 すでにこれを行っています。 createModelAtLocation()関数でそれをラップし、トリガーを追加してジオにアタッチします。
-特定の地点までの距離を追跡し、適切な措置を講じます。
| // inject poi |
| loadPoisFromJsonData: function loadPoisFromJsonDataFn() { |
| var markerAsset = new AR.ImageResource("assets/marker_idle.png"); |
| var markerImageDrawable_idle = new AR.ImageDrawable(markerAsset, 2.5, { |
| zOrder: 0, |
| opacity: 1.0 |
| }); |
| |
| // geo object . , |
| World.targetPOIObject = new AR.GeoObject(World.targetLocation, { |
| drawables: { |
| cam: [markerImageDrawable_idle], |
| indicator: [World.indicatorDrawable] |
| } |
| }); |
| }, |
| |
| createModelAtLocation: function createModelAtLocationFn() { |
| modelAsset = new AR.Model("assets/car.wt3", { |
| onLoaded: this.onModelLoaded, |
| onClick: this.onModelClick, |
| scale: { |
| x: 0.6, |
| y: 0.6, |
| z: 0.6 |
| }, |
| }); |
| |
| // |
| var relativeLoc = new AR.RelativeLocation(null, 4, 0, -4); |
| World.targetGeoObject = new AR.GeoObject(relativeLoc, { |
| drawables: { |
| cam: [modelAsset], |
| indicator: [World.indicatorDrawable] |
| } |
| }); |
| }, |
| |
| // , , , poi |
| stateOnDistance: function stateOnDistanceFn() { |
| var distance = World.targetLocation.distanceToUser(); |
| |
| var e = document.getElementById('loadingMessage'); |
| if (distance < 100) |
| { |
| if (World.targetGeoObject != true) |
| { |
| World.targetGeoObject.enabled = true; |
| World.targetPOIObject.enabled = false; |
| e.innerHTML = " ! !"; |
| } |
| } |
| else |
| { |
| if (World.targetPOIObject != true) |
| { |
| World.targetGeoObject.enabled = false; |
| World.targetPOIObject.enabled = true; |
| } |
| |
| e.innerHTML = distance + " "; |
| } |
| }, |
| |
| |
| var relativeLoc = new AR.RelativeLocation(null, 4, 0, -4); |
| World.targetGeoObject = new AR.GeoObject(relativeLoc, { |
| drawables: { |
| cam: [modelAsset], |
| indicator: [World.indicatorDrawable] |
| } |
| }); |
| }, |
私たちからの距離で私たちはPOIを見る
オブジェクトにズームインして、一定の回転アニメーションで3Dモデルを見るケースに関する作業をまとめましょう。 取得したものは次のとおりです。
-パターン認識。
-認識の代わりに3Dモデルにアニメーションをオーバーレイします。
-地理位置情報で私たちから遠く、POIとして反映される3Dモデル。
-接近時のPOIの3Dモデルへの変換。
-クリックの処理、ラベルの表示。
-地理参照ではなく、3Dモデルが地理的位置に関連付けられ、相対的な位置に配置されます。
同時に、認識のために画像を読み込んだり、3Dモデル/アニメーションを読み込んだりするのにまったく問題はありませんでした。 全体のプロセスは直感的でシンプルです。
私が書いたすべてのものの有用な使用法を見つけていただければ幸いです。 これが唯一の選択肢ではないことを理解しています。 したがって、私はコメントであなたの提案を喜んでいるでしょう。
みんなのための拡張現実、平和とチューインガム!
PS最初は、このトピックが3つの記事にまで及ぶとは思いませんでした。 パラドックスは、これがすべてとは程遠いということです。 この分野は非常に興味深く、困難です。 昨日、GoogleはGoogle Tangoの廃止を発表しました。ARCoreは素晴らしい仕事をしているため、不要になりました。 ニューラルネットワーク市場で何が起こっているかは問題外です。
投稿者Vitaliy Zarubin、
Reksoftシニア開発エンジニア