Files
deighton-ar/mobile/src/ARViewer/ARViewer.js
John Lyon-Smith bb8c33846a Tweak to AR view
2018-05-16 17:15:51 -07:00

269 lines
7.2 KiB
JavaScript

import React from "react"
import { StyleSheet, View, TouchableHighlight, Image } from "react-native"
import {
ViroARSceneNavigator,
ViroARScene,
ViroARPlane,
ViroAmbientLight,
ViroDirectionalLight,
ViroMaterials,
Viro3DObject,
ViroSpotLight,
ViroNode,
ViroQuad,
ViroConstants,
} from "react-viro"
import autobind from "autobind-decorator"
import backImage from "./images/back.png"
import { config } from "../config"
import "url-search-params-polyfill"
const shapes = {
order: {
source: require("./models/hardhat_obj.obj"),
resources: [require("./models/hardhat.mtl")],
scale: [0.5, 0.5, 0.5],
},
complaint: {
source: require("./models/question_obj.obj"),
resources: [require("./models/question.mtl")],
scale: [1, 1, 1],
},
inspection: {
source: require("./models/clipboard_obj.obj"),
resources: [require("./models/clipboard.mtl")],
scale: [1, 1, 1],
},
}
const distance = (vectorA, vectorB) => {
return Math.sqrt(
(vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) +
(vectorB[1] - vectorA[1]) * (vectorB[1] - vectorA[1]) +
(vectorB[2] - vectorA[2]) * (vectorB[2] - vectorA[2])
)
}
class WorkItemSceneAR extends React.Component {
constructor(props) {
super(props)
this.state = {
position: [0, 0, 0],
rotation: [0, 0, 0],
shouldBillboard: true,
trackingInitialized: false,
haveAnchor: false,
}
}
@autobind
handleLoadEnd() {
this.arScene.getCameraOrientationAsync().then((orientation) => {
return this.arScene
.performARHitTestWithRay(orientation.forward)
.then((results) => {
const { forward, position } = orientation
// Default position is just one meter in front of the user
const defaultPosition =
[forward[0] * 1.0, forward[1] * 1.0, forward[2]] * 1.0
let hitResultPosition = null
// Filter the hit test results based on the position.
for (var i = 0; i < results.length; i++) {
let result = results[i]
if (result.type == "ExistingPlaneUsingExtent") {
let distance = Math.sqrt(
(result.transform.position[0] - position[0]) *
(result.transform.position[0] - position[0]) +
(result.transform.position[1] - position[1]) *
(result.transform.position[1] - position[1]) +
(result.transform.position[2] - position[2]) *
(result.transform.position[2] - position[2])
)
if (distance > 1 && distance < 15) {
// If we found a plane a decent distance away, choose it!
hitResultPosition = result.transform.position
break
}
} else if (result.type == "FeaturePoint" && !hitResultPosition) {
// If we haven't found a plane and this feature point is within range,
// then we'll use it as the initial display point
let d = distance(position, result.transform.position)
if (d > 1 && d < 15) {
hitResultPosition = result.transform.position
}
}
}
this.setState({
position: hitResultPosition || defaultPosition,
})
setTimeout(() => {
this.updateInitialRotation()
}, 200)
})
.catch((err) => {})
})
}
@autobind
updateInitialRotation() {
this.arNode.getTransformAsync().then((retDict) => {
let rotation = retDict.rotation
let absX = Math.abs(rotation[0])
let absZ = Math.abs(rotation[2])
let yRotation = rotation[1]
// If the X and Z aren't 0, then adjust the y rotation.
if (absX > 1 && absZ > 1) {
yRotation = 180 - yRotation
}
this.setState({
rotation: [0, yRotation, 0],
shouldBillboard: false,
})
})
}
@autobind
handleTrackingUpdated(state, reason) {
if (
!this.state.trackingInitialized &&
state === ViroConstants.TRACKING_NORMAL
) {
this.setState({ trackingInitialized: true })
}
}
@autobind
handleAnchorFound() {
this.setState({ haveAnchor: true })
}
@autobind
handleClick(position, source) {
const { workItemId } = this.props
this.props.history.replace(
`/activity${workItemId ? "?workItemId=" + workItemId : ""}`
)
}
render() {
const {
position,
scale,
rotation,
shouldBillboard,
trackingInitialized,
haveAnchor,
} = this.state
const shape = shapes[this.props.workItemType]
return (
<ViroARScene
anchorDetectionTypes="PlanesHorizontal"
ref={(ref) => (this.arScene = ref)}
onTrackingUpdated={this.handleTrackingUpdated}
onAnchorFound={this.handleAnchorFound}>
<ViroAmbientLight color="#ffffff" intensity={200} />
<ViroNode
ref={(ref) => (this.arNode = ref)}
transformBehaviors={shouldBillboard ? "billboardY" : []}
position={position}
rotation={rotation}>
<ViroSpotLight
innerAngle={5}
outerAngle={20}
direction={[0, -1, 0]}
position={[0, 10, 0]}
color="#ffffff"
castsShadow={true}
shadowNearZ={0.1}
shadowFarZ={6}
shadowOpacity={0.9}
/>
{shape &&
trackingInitialized &&
haveAnchor && (
<Viro3DObject
position={[0, 0, -1]}
source={shape.source}
resources={shape.resources}
scale={shape.scale}
type="OBJ"
onLoadEnd={this.handleLoadEnd}
onClick={this.handleClick}
/>
)}
<ViroQuad
rotation={[-90, 0, 0]}
position={[0, -0.01, 0]}
width={2.5}
height={2.5}
arShadowReceiver={true}
ignoreEventHandling={true}
/>
</ViroNode>
</ViroARScene>
)
}
}
export class ARViewer extends React.Component {
constructor(props) {
super(props)
const { search } = this.props.location
if (search) {
const params = new URLSearchParams(search)
this.workItemId = params.get("workItemId")
this.workItemType = params.get("workItemType")
}
}
@autobind
handleBackPress() {
this.props.history.replace("/")
}
render() {
return (
<View style={{ width: "100%", height: "100%" }}>
<ViroARSceneNavigator
style={{ width: "100%", height: "100%" }}
apiKey={config.viroAPIKey}
initialScene={{
scene: WorkItemSceneAR,
passProps: {
history: this.props.history,
workItemId: this.workItemId,
workItemType: this.workItemType,
},
}}
/>
<View style={{ position: "absolute", left: 30, right: 0, top: 50 }}>
<TouchableHighlight
style={{
height: 80,
width: 80,
}}
onPress={this.handleBackPress}
underlayColor={"#00000000"}>
<Image source={backImage} />
</TouchableHighlight>
</View>
</View>
)
}
}