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
32 changes: 32 additions & 0 deletions src/gesture-action/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Mind AR TS - A-Frame Gestures

This A-Frame Gestures is taken from [AR.js Gestures](https://github.com/fcor/arjs-gestures).

<p align="center"><img width="400" alt="gesture sample" src="https://user-images.githubusercontent.com/21111451/83983551-00accd00-a8f5-11ea-80a6-e075971ba1d2.gif"></p>

A-Frame Gestures for Mind AR TS. This work is based on [AR.js Gestures](https://github.com/fcor/arjs-gestures) from @fcor and [this example](https://github.com/8thwall/web/blob/master/examples/aframe/manipulate/README.md) from 8th Wall.

Detect any gesture or mouse action on your Mind AR TS Scene using `gesture-detector` and `mouse-detector` components. Scale and rotate 3D elements from your Mind AR TS scene using `gesture-rotation`, `gesture-scale`, `mouse-rotation`, and `mouse-scale` for scaling and rotating the model in AR Scene.

## Properties

### Gesture/Mouse Detector

| Property | Description | Default value |
| -------- | ------------------------------------- | ------------- |
| enabled | Enable detecting gesture/mouse action | true |

### Gesture/Mouse Rotation

| Property | Description | Default value |
| -------------- | ----------------------- | ------------- |
| enabled | Enable rotation | true |
| rotationFactor | Multiplier for rotation | 5 |

### Gesture/Mouse Scale

| Property | Description | Default value |
| -------- | --------------------------- | ------------- |
| enabled | Enable scaling | true |
| minScale | Minimal scale of the entity | 0.3 |
| maxScale | Maximum scale of the entity | 8 |
6 changes: 6 additions & 0 deletions src/gesture-action/aframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import './aframe/gesture-detector';
import './aframe/gesture-rotation';
import './aframe/gesture-scale';
import './aframe/mouse-detector';
import './aframe/mouse-rotation';
import './aframe/mouse-scale';
149 changes: 149 additions & 0 deletions src/gesture-action/aframe/gesture-detector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Entity } from 'aframe';
import { Helper } from '../../libs';
import { GESTURE_COMPONENT } from '../utils/constant';
import { ITouchState } from '../utils/types/aframe';

type InternalState = ITouchState | null;

AFRAME.registerComponent(GESTURE_COMPONENT.GESTURE_DETECTOR, {
targetElement: Helper.castTo<Entity>(null),
internalState: {
previousState: null as InternalState,
currentState: null as InternalState,
},

schema: {
enabled: { type: 'boolean', default: true },
},

init: function () {
if (!this.targetElement) this.targetElement = this.el;

this.targetElement.addEventListener('touchstart', this.onTouch.bind(this));
this.targetElement.addEventListener('touchend', this.onTouch.bind(this));
this.targetElement.addEventListener('touchmove', this.onTouch.bind(this));
},

onTouch: function (ev: Event) {
if (!this.data.enabled) return;

const event = ev as TouchEvent;

const currentState = this.getTouchState(event);
const previousState = this.internalState.previousState;

const havePrevCurrState = previousState && currentState;
const haveSameTouchCount = currentState?.count == previousState?.count;

const gestureContinues = havePrevCurrState && haveSameTouchCount;
const gestureEnded = previousState && !gestureContinues;
const gestureStarted = !previousState && currentState;

if (gestureEnded) this.onTouchEnd(previousState);
if (gestureStarted) this.onTouchStart(currentState);
if (gestureContinues) this.onTouchMove(previousState, currentState);
},

onTouchStart: function (currentState: ITouchState) {
currentState.startTime = performance.now();
currentState.startPosition = currentState.position;
currentState.startSpread = currentState.spread;

const eventName = this.getEventPrefix(currentState.count) + 'fingerstart';

this.el.emit(eventName, currentState);

this.internalState.previousState = currentState;
},

onTouchEnd: function (previousState: ITouchState) {
const eventName = this.getEventPrefix(previousState.count) + 'fingerend';

this.el.emit(eventName, previousState);

this.internalState.previousState = null;
},

onTouchMove: function (previousState: ITouchState, currentState: ITouchState) {
const eventDetail = {
positionChange: {
x: currentState.position.x - previousState.position.x,

y: currentState.position.y - previousState.position.y,
},
spreadChange: 0,
};

if (!Helper.isNil(currentState?.spread))
eventDetail.spreadChange = currentState.spread - (previousState.spread ?? 0);

// Update state with new data
Object.assign(previousState, currentState);

// Add state data to event detail
Object.assign(eventDetail, previousState);

const eventName = this.getEventPrefix(currentState.count) + 'fingermove';
this.el.emit(eventName, eventDetail);
},

getTouchState: function (ev: TouchEvent) {
if (ev.touches.length === 0) return null;

const zeroPosXY = {
x: 0,
y: 0,
};

const touchState: ITouchState = {
count: ev.touches.length,
rawPosition: zeroPosXY,
position: zeroPosXY,
};

// Get all touches on the target element.
const touches: Touch[] = [];

for (let i = 0; i < ev.touches.length; i++) {
touches.push(ev.touches[i]);
}

// Get all raw positions from touches
const averageRawX = touches.reduce((acc, cur) => acc + cur.clientX, 0) / touches.length;
const averageRawY = touches.reduce((acc, curr) => acc + curr.clientY, 0) / touches.length;

touchState.rawPosition = {
x: averageRawX,
y: averageRawY,
};

// Get the scale of touch positions
const scale = 2 / (window.innerHeight + window.innerWidth);

touchState.position = {
x: averageRawX * scale,
y: averageRawY * scale,
};

if (touches.length == 1) return touchState;

// Get the spread of touch positions
const spread =
touches.reduce((acc, cur) => {
const xSqr = Math.pow(averageRawX - cur.clientX, 2);
const ySqr = Math.pow(averageRawY - cur.clientY, 2);

return acc + Math.sqrt(xSqr + ySqr);
}, 0) / touches.length;

touchState.spread = spread * scale;

return touchState;
},

getEventPrefix: function (touchCount: number) {
const names = ['one', 'two', 'three', 'many'];

return names[Math.min(touchCount, 4) - 1];
},
});
43 changes: 43 additions & 0 deletions src/gesture-action/aframe/gesture-rotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Entity } from 'aframe';
import { AR_EVENT_NAME } from '../../image-target/utils/constant/aframe';
import { Helper } from '../../libs';
import { GESTURE_COMPONENT, GESTURE_EVENT_NAME } from '../utils/constant';

AFRAME.registerComponent(GESTURE_COMPONENT.GESTURE_HANDLER.ROTATION, {
dependencies: [GESTURE_COMPONENT.GESTURE_DETECTOR],
isVisible: false,
el: Helper.castTo<Entity>(null),

schema: {
enabled: { type: 'boolean', default: true },
rotationFactor: { type: 'number', default: 5 },
locationBased: { type: 'boolean', default: false },
},

init: function () {
if (!this.el.sceneEl) return;

this.el.sceneEl.addEventListener(AR_EVENT_NAME.MARKER_FOUND, () => {
if (!this.el.sceneEl) return;

this.el.sceneEl.addEventListener(
GESTURE_EVENT_NAME.ONE_FINGER_MOVE,
this.onRotation.bind(this)
);
});

this.el.sceneEl.addEventListener(AR_EVENT_NAME.MARKER_LOST, () => {
if (!this.el.sceneEl) return;

this.el.sceneEl.removeEventListener(GESTURE_EVENT_NAME.ONE_FINGER_MOVE, this.onRotation);
});
},

onRotation: function (event: Event) {
if (!this.data.enabled) return;
if (!event.detail.positionChange) return;

this.el.object3D.rotation.y += event.detail.positionChange.x * this.data.rotationFactor;
this.el.object3D.rotation.x += event.detail.positionChange.y * this.data.rotationFactor;
},
});
60 changes: 60 additions & 0 deletions src/gesture-action/aframe/gesture-scale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Entity } from 'aframe';
import { AR_EVENT_NAME } from '../../image-target/utils/constant/aframe';
import { Helper } from '../../libs';
import { GESTURE_COMPONENT, GESTURE_EVENT_NAME } from '../utils/constant';

AFRAME.registerComponent(GESTURE_COMPONENT.GESTURE_HANDLER.SCALE, {
dependencies: [GESTURE_COMPONENT.GESTURE_DETECTOR],
isVisible: false,
scaleFactor: 1,
initialScale: { x: 0, y: 0, z: 0 } as Vector3,

el: Helper.castTo<Entity>(null),

schema: {
enabled: { type: 'boolean', default: true },
minScale: { type: 'number', default: 0.3 },
maxScale: { type: 'number', default: 8 },
locationBased: { type: 'boolean', default: false },
},

init: function () {
if (!this.el.sceneEl) return;

this.isVisible = this.data.locationBased;
this.initialScale = this.el.object3D.scale.clone();

this.el.sceneEl.addEventListener(AR_EVENT_NAME.MARKER_FOUND, () => {
if (!this.el.sceneEl) return;

this.el.sceneEl.addEventListener(
GESTURE_EVENT_NAME.TWO_FINGER_MOVE,
this.onScaling.bind(this)
);
});

this.el.sceneEl.addEventListener(AR_EVENT_NAME.MARKER_LOST, () => {
if (!this.el.sceneEl) return;

this.el.sceneEl.removeEventListener(GESTURE_EVENT_NAME.TWO_FINGER_MOVE, this.onScaling);
});
},

onScaling: function (event: Event) {
if (!this.data.enabled) return;
if (!event.detail.spreadChange || !event.detail.startSpread) return;

const { spreadChange, startSpread } = event.detail;

this.scaleFactor *= 1 + spreadChange / startSpread;

// Clamp scale factor.
this.scaleFactor = Math.min(this.data.maxScale, Math.max(this.data.minScale, this.scaleFactor));
this.el.object3D.scale.set(
this.initialScale.x * this.scaleFactor,
this.initialScale.y * this.scaleFactor,
this.initialScale.z * this.scaleFactor
);
},
});
Loading