Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/experimental/geo-location/aframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './aframe/location';
import './aframe/location-camera';
import './aframe/location-place';
import './aframe/location-system';
48 changes: 48 additions & 0 deletions src/experimental/geo-location/aframe/location-camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Helper } from '../../../libs';
import { AR_COMPONENT_NAME, SYSTEM_STATE } from '../../../geo-location/utils/constant';

AFRAME.registerComponent(AR_COMPONENT_NAME.LOCATION_CAMERA, {
dependencies: [AR_COMPONENT_NAME.LOCATION_SYSTEM],

arSystem: null as any,
el: null as any,

schema: {
shouldFaceUser: { type: 'boolean', default: false },
simulateAltitude: { type: 'number', default: 0 },
simulateLatitude: { type: 'number', default: 0 },
simulateLongitude: { type: 'number', default: 0 },
positionMinAccuracy: { type: 'int', default: 100 },
minDistance: { type: 'int', default: 0 },
maxDistance: { type: 'int', default: 0 },
gpsMinDistance: { type: 'number', default: 3 },
gpsTimeInterval: { type: 'number', default: 3 },
},

init: function () {
const arSystem = this.el.sceneEl.systems[AR_COMPONENT_NAME.LOCATION_SYSTEM];
this.arSystem = arSystem;

this.el.sceneEl.addEventListener(SYSTEM_STATE.LOCATION_INITIALIZED, this.setup.bind(this));
},

tick: function () {
if (!this.arSystem || !this.arSystem.controller) return;

if (Helper.isNil(this.arSystem.controller.camera.heading === null)) return;

this.arSystem.controller.updateRotation();
},

setup: function () {
this.arSystem.setupCamera({
...this.data,
camera: this.el,
});

console.log('Location camera initialized');

this.el.sceneEl.removeEventListener(SYSTEM_STATE.LOCATION_INITIALIZED, this.setup);
},
});
53 changes: 53 additions & 0 deletions src/experimental/geo-location/aframe/location-place.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
AR_COMPONENT_NAME,
AR_EVENT_NAME,
SYSTEM_STATE,
} from '../../../geo-location/utils/constant';

AFRAME.registerComponent(AR_COMPONENT_NAME.LOCATION_PLACE, {
dependencies: [AR_COMPONENT_NAME.LOCATION_SYSTEM],

el: null as any,

schema: {
longitude: { type: 'number', default: 0 },
latitude: { type: 'number', default: 0 },
placeIndex: { type: 'number' },
minDistance: { type: 'number', default: -1 },
maxDistance: { type: 'number', default: -1 },
},

init: function () {
// Clamp minDistance and maxDistance
this.data.minDistance = Math.max(-1, this.data.minDistance);
this.data.maxDistance = Math.max(-1, this.data.maxDistance);

// Trigger the event only after the camera is initialized
this.el.sceneEl.addEventListener(SYSTEM_STATE.CAMERA_INITIALIZED, this.setup.bind(this));
this.el.sceneEl.addEventListener(AR_EVENT_NAME.LOCATION_FOUND, this.onLocationFound.bind(this));
this.el.sceneEl.addEventListener(AR_EVENT_NAME.LOCATION_LOST, this.onLocationLost.bind(this));
},

setup: function () {
const arSystem = this.el.sceneEl.systems[AR_COMPONENT_NAME.LOCATION_SYSTEM];
arSystem.addLocation({
...this.data,
location: this.el,
});

console.log('Location place added');

this.el.sceneEl.removeEventListener(SYSTEM_STATE.CAMERA_INITIALIZED, this.setup);
},

onLocationFound: function (event: Event) {
if (event.detail.placeIndex !== this.data.placeIndex) return;
console.log(event.detail);
},

onLocationLost: function (event: Event) {
if (event.detail.placeIndex !== this.data.placeIndex) return;
console.log(event.detail);
},
});
164 changes: 164 additions & 0 deletions src/experimental/geo-location/aframe/location-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Stats from 'stats-js';
import { Scene } from 'aframe';
import { UI } from '../../../ui/ui';
import Controller from '../controller';
import { Helper } from '../../../libs';
import {
AR_ELEMENT_TAG,
AR_STATE,
GLOBAL_AR_EVENT_NAME,
STATS_STYLE,
} from '../../../utils/constant';
import { AR_COMPONENT_NAME, SYSTEM_STATE } from '../../../geo-location/utils/constant';
import screenResizer from '../../../utils/screen-resizer';
import {
CameraTrackerConstructor,
LocationTrackerConstructor,
} from '../../../geo-location/utils/types/geo-location';

const { Controller: ControllerClass, UI: UIClass } = window.MINDAR.LOCATION;

AFRAME.registerSystem(AR_COMPONENT_NAME.LOCATION_SYSTEM, {
container: Helper.castTo<HTMLDivElement>(null),
video: Helper.castTo<HTMLVideoElement>(null),
el: null as any,
showStats: false,
controller: Helper.castTo<Controller>(null),
mainStats: Helper.castTo<Stats>(null),
ui: Helper.castTo<UI>(null),
sytemState: SYSTEM_STATE.LOCATION_INITIALIZING,
shouldFaceUser: false,
isEmulated: false,

setup: function ({
showStats,
uiLoading,
uiScanning,
uiError,
}: {
uiLoading: string;
uiScanning: string;
uiError: string;
showStats: boolean;
}) {
this.showStats = showStats;

this.ui = new UIClass({ uiLoading, uiScanning, uiError });
this.controller = new ControllerClass() as any;

this.el.emit(SYSTEM_STATE.LOCATION_INITIALIZED);
},

setupCamera: function (props: Omit<CameraTrackerConstructor, 'controller'>) {
if (!this.controller) return;

// Prevent to register multiple cameras
if (this.controller.camera) return;

this.el.emit(SYSTEM_STATE.CAMERA_INITIALIZING);

this.controller.setupCamera(props);

this.isEmulated = props.simulateLatitude !== 0 && props.simulateLongitude !== 0;

this.el.emit(SYSTEM_STATE.CAMERA_INITIALIZED);
},

addLocation: function (props: Omit<LocationTrackerConstructor, 'controller'>) {
if (!this.controller) return;

this.controller.addLocation(props);
},

start: function () {
this.container = this.el.sceneEl.parentNode;

if (this.showStats) {
this.mainStats = new Stats();
this.mainStats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
this.mainStats.domElement.style.cssText = STATS_STYLE;
this.container.appendChild(this.mainStats.domElement);
}

this.ui.showLoading();
this._startVideo();
},

_startVideo: async function () {
this.video = Helper.castTo<HTMLVideoElement>(document.createElement('video'));

this.video.setAttribute('autoplay', '');
this.video.setAttribute('muted', '');
this.video.setAttribute('playsinline', '');

this.video.style.position = 'absolute';
this.video.style.top = '0px';
this.video.style.left = '0px';
this.video.style.zIndex = '-2';

this.container.appendChild(this.video);

if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
// TODO: show unsupported error
this.el.emit(AR_STATE.AR_ERROR, { error: 'VIDEO_FAIL' });
this.ui.showCompatibility();

return;
}

try {
const DEVICES = await navigator.mediaDevices.enumerateDevices();
const devices = DEVICES.filter((device) => device.kind === 'videoinput');

let facingMode: VideoFacingModeEnum = 'environment';

if (devices.length > 1) facingMode = this.shouldFaceUser ? 'user' : 'environment';

const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
facingMode,
},
});

this.video.addEventListener(GLOBAL_AR_EVENT_NAME.LOADED_METADATA, () => {
this.video.setAttribute('width', this.video.videoWidth.toString());
this.video.setAttribute('height', this.video.videoHeight.toString());

this._startAR();
});

this.video.srcObject = stream;
} catch (err) {
console.log('getUserMedia error', err);
this.el.emit(AR_STATE.AR_ERROR, { error: 'VIDEO_FAIL' });
}
},

_startAR: function () {
this._resize();
window.addEventListener(GLOBAL_AR_EVENT_NAME.SCREEN_RESIZE, this._resize.bind(this));

this.el.emit(AR_STATE.AR_READY);
this.controller.startAR();
this.ui.hideLoading();
},

_resize: function () {
screenResizer(this.video, this.container);

const sceneEl = this.container.getElementsByTagName(AR_ELEMENT_TAG.A_SCENE)[0] as Scene;

sceneEl.style.top = this.video.style.top;
sceneEl.style.left = this.video.style.left;
sceneEl.style.width = this.video.style.width;
sceneEl.style.height = this.video.style.height;
},

update: function () {
if (!this.isEmulated) return;

this.controller.camera.getEmulatedPosition();
},
});
33 changes: 33 additions & 0 deletions src/experimental/geo-location/aframe/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AR_STATE } from '../../../utils/constant';
import { AR_COMPONENT_NAME } from '../../../geo-location/utils/constant';

AFRAME.registerComponent(AR_COMPONENT_NAME.LOCATION, {
dependencies: [AR_COMPONENT_NAME.LOCATION_SYSTEM],

el: null as any,

schema: {
showStats: { type: 'boolean', default: false },
autoStart: { type: 'boolean', default: true },
uiLoading: { type: 'string', default: 'yes' },
uiScanning: { type: 'string', default: 'yes' },
uiError: { type: 'string', default: 'yes' },
},

init: function () {
const arSystem = this.el.sceneEl.systems[AR_COMPONENT_NAME.LOCATION_SYSTEM];

arSystem.setup({
uiLoading: this.data.uiLoading,
uiScanning: this.data.uiScanning,
uiError: this.data.uiError,
showStats: this.data.showStats,
});

if (this.data.autoStart)
this.el.sceneEl.addEventListener(AR_STATE.RENDER_START, () => {
arSystem.start();
});
},
});
68 changes: 68 additions & 0 deletions src/experimental/geo-location/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { haversineDist } from '../../geo-location/utils/distance';
import {
CameraTrackerConstructor,
HaversineParams,
LocationTrackerConstructor,
} from '../../geo-location/utils/types/geo-location';
import { Helper } from '../../libs';
import CameraTracker from './tracker/camera';
import LocationTracker from './tracker/location';

class Controller {
private minDistance: number;
private maxDistance: number;
public location: LocationTracker[];
public camera!: CameraTracker;

constructor(minDistance: number, maxDistance: number) {
this.minDistance = minDistance;
this.maxDistance = maxDistance;

this.location = [];
}

public setupCamera(cameraParams: Omit<CameraTrackerConstructor, 'controller'>) {
this.camera = new CameraTracker({ ...cameraParams, controller: this as any });
this.minDistance = cameraParams.minDistance ?? 0;
this.maxDistance = cameraParams.maxDistance ?? 0;
}

public addLocation(locationParams: Omit<LocationTrackerConstructor, 'controller'>) {
const location = new LocationTracker({
...locationParams,
controller: this as any,
camera: this.camera.camera,
});

this.location.push(location);
}

public computeDistanceMeters(src: HaversineParams, dest: HaversineParams, isPlace = false) {
const distance = haversineDist(src, dest);

const minDist = this.minDistance;
const maxDist = this.maxDistance;

// * If its a place and the distance is too near to the user,
// * we'll return the maximum distance as possible and let the caller handle it
if (isPlace && minDist > 0 && distance < minDist) return Number.MAX_SAFE_INTEGER;

// * If its a place and the distance is too far to the user,
// * we'll return the maximum distance as possible and let the caller handle it
if (isPlace && maxDist > 0 && distance > maxDist) return Number.MAX_SAFE_INTEGER;

return distance;
}

public startAR() {
this.camera.startAR();
}

public updateRotation() {
if (Helper.isNil(this.camera)) return;

this.camera.updateRotation();
}
}

export default Controller;
16 changes: 16 additions & 0 deletions src/experimental/geo-location/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { UI } from '../../ui/ui';
import Controller from './controller';

const geoLocation = {
Controller,
UI,
};

if (!window.MINDAR) window.MINDAR = {} as typeof window.MINDAR;

if (!window.MINDAR.LOCATION) window.MINDAR.LOCATION = geoLocation as typeof window.MINDAR.LOCATION;

if (!window.MINDAR.LOCATION.UI) window.MINDAR.LOCATION.UI = UI;
if (!window.MINDAR.LOCATION.Controller) window.MINDAR.LOCATION.Controller = Controller as any;

export default geoLocation;
Loading