301 lines
8 KiB
JavaScript
301 lines
8 KiB
JavaScript
|
define((require, exports, module) => {
|
||
|
const storeManager = require('State')
|
||
|
const robotStore = require('Robot')
|
||
|
const {
|
||
|
scene,
|
||
|
camera,
|
||
|
renderer,
|
||
|
} = require('THREEScene')
|
||
|
|
||
|
/**
|
||
|
* + state per module
|
||
|
* + render on state changed
|
||
|
* + get state from other modules
|
||
|
*
|
||
|
* --- onStore update render ---
|
||
|
* get data From other stores
|
||
|
* - store might not have changed, so no update
|
||
|
*/
|
||
|
|
||
|
const defaultState = {
|
||
|
controlSpace: 'world',
|
||
|
eulerRingsVisible: false,
|
||
|
controlVisible: true,
|
||
|
controlMode: 'translate',
|
||
|
followTarget: true,
|
||
|
manipulate: 'rotate',
|
||
|
position: {
|
||
|
x: 10,
|
||
|
y: 10,
|
||
|
z: 10,
|
||
|
},
|
||
|
rotation: {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
z: 0,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
const store = storeManager.createStore('Target', defaultState)
|
||
|
|
||
|
store.action('CHANGE_FOLLOW_TARGET', (state, data) => {
|
||
|
if (data) {
|
||
|
store.getStore('Robot').dispatch('ROBOT_CHANGE_TARGET', {
|
||
|
position: state.position,
|
||
|
rotation: state.rotation,
|
||
|
})
|
||
|
}
|
||
|
return Object.assign({}, state, {
|
||
|
followTarget: data,
|
||
|
})
|
||
|
})
|
||
|
|
||
|
store.action('SET_EULER_RINGS_VISIBLE', (state, data) => Object.assign({}, state, {
|
||
|
eulerRingsVisible: data,
|
||
|
}))
|
||
|
store.action('SET_CONTROL_VISIBLE', (state, data) => Object.assign({}, state, {
|
||
|
controlVisible: data,
|
||
|
}))
|
||
|
|
||
|
const sphereGeo = new THREE.CylinderGeometry(1, 1, 1 * 2, 32)
|
||
|
const target = new THREE.Group()
|
||
|
|
||
|
const targetCylinder = new THREE.Mesh(sphereGeo, new THREE.MeshBasicMaterial({
|
||
|
transparent: true,
|
||
|
opacity: 0.7,
|
||
|
color: 0xaaaaaa,
|
||
|
}))
|
||
|
targetCylinder.rotation.x = Math.PI / 2
|
||
|
targetCylinder.position.z += 1
|
||
|
target.add(targetCylinder)
|
||
|
const arrowZ = new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 4, 0x0000ff)
|
||
|
// arrowZ.line.material.linewidth = 4
|
||
|
target.add(arrowZ)
|
||
|
const arrowY = new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), 4, 0x00ff00)
|
||
|
// arrowY.line.material.linewidth = 4
|
||
|
target.add(arrowY)
|
||
|
const arrowX = new THREE.ArrowHelper(new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), 4, 0xff0000)
|
||
|
// arrowX.line.material.linewidth = 4
|
||
|
//
|
||
|
target.rotation.y = Math.PI / 2
|
||
|
target.rotation.z = -Math.PI
|
||
|
target.add(arrowX)
|
||
|
// const axisHelper = new THREE.AxisHelper(5)
|
||
|
// target.add(axisHelper)
|
||
|
// ZYX fixed axis -> XYZ moving axis
|
||
|
target.rotation.order = 'XYZ'
|
||
|
scene.add(target)
|
||
|
|
||
|
/* CONTROLS */
|
||
|
|
||
|
function createRing(radius, color, axis) {
|
||
|
const sphere_radius = 0.12
|
||
|
|
||
|
const ringMaterial = new THREE.MeshLambertMaterial({
|
||
|
color,
|
||
|
})
|
||
|
|
||
|
// create ring shape
|
||
|
const circleMesh = new THREE.Mesh(
|
||
|
new THREE.TorusGeometry(radius, 0.05, 6, 50),
|
||
|
ringMaterial,
|
||
|
)
|
||
|
|
||
|
const sphereMesh = new THREE.Mesh(
|
||
|
new THREE.SphereGeometry(sphere_radius, 12, 10),
|
||
|
ringMaterial,
|
||
|
)
|
||
|
sphereMesh.position.x = radius
|
||
|
|
||
|
const composite = new THREE.Object3D()
|
||
|
composite.add(circleMesh)
|
||
|
composite.add(sphereMesh)
|
||
|
// composite.add(coneMesh)
|
||
|
|
||
|
if (axis === 'x') {
|
||
|
composite.rotation.y = Math.PI / 2
|
||
|
} else if (axis === 'y') {
|
||
|
composite.rotation.x = Math.PI / 2
|
||
|
}
|
||
|
|
||
|
const ringObj = new THREE.Object3D()
|
||
|
ringObj.add(composite)
|
||
|
|
||
|
return ringObj
|
||
|
}
|
||
|
|
||
|
let ringx
|
||
|
let ringy
|
||
|
let ringz
|
||
|
// Euler https://www.udacity.com/course/viewer#!/c-cs291/l-91073092/m-123949249
|
||
|
function createAllRings(parentObject) {
|
||
|
// debugger
|
||
|
// create Rings
|
||
|
ringx = createRing(2.00, 0xFF0000, 'x')
|
||
|
ringy = createRing(1.75, 0x00FF00, 'y')
|
||
|
ringz = createRing(1.50, 0x0000FF, 'z')
|
||
|
|
||
|
// set up rotation hierarchy - assuming x -> y -> z intrinsic
|
||
|
// ringy.add(ringx)
|
||
|
// ringz.add(ringy)
|
||
|
//
|
||
|
// parentObject.add(ringz)
|
||
|
|
||
|
ringy.add(ringz)
|
||
|
ringx.add(ringy)
|
||
|
|
||
|
parentObject.add(ringx)
|
||
|
}
|
||
|
|
||
|
const eulerRings = new THREE.Object3D()
|
||
|
scene.add(eulerRings)
|
||
|
createAllRings(eulerRings)
|
||
|
|
||
|
const control = new THREE.TransformControls(camera, renderer.domElement)
|
||
|
let disableUpdate = false
|
||
|
store.listen([() => store.getStore('Robot').getState().target, state => state], (targetT, state) => {
|
||
|
if (state.followTarget) {
|
||
|
state.position.x = targetT.position.x
|
||
|
state.position.y = targetT.position.y
|
||
|
state.position.z = targetT.position.z
|
||
|
|
||
|
state.rotation.x = targetT.rotation.x
|
||
|
state.rotation.y = targetT.rotation.y
|
||
|
state.rotation.z = targetT.rotation.z
|
||
|
}
|
||
|
|
||
|
target.position.x = state.position.x
|
||
|
target.position.y = state.position.y
|
||
|
target.position.z = state.position.z
|
||
|
|
||
|
target.rotation.x = state.rotation.x
|
||
|
target.rotation.y = state.rotation.y
|
||
|
target.rotation.z = state.rotation.z
|
||
|
|
||
|
|
||
|
if (true) { // loop - changing mode triggers change....
|
||
|
disableUpdate = true
|
||
|
control.setMode(state.controlMode)
|
||
|
control.setSpace(state.controlSpace)
|
||
|
disableUpdate = false
|
||
|
}
|
||
|
|
||
|
control.visible = state.controlVisible
|
||
|
eulerRings.visible = state.eulerRingsVisible
|
||
|
|
||
|
eulerRings.position.set(target.position.x, target.position.y, target.position.z)
|
||
|
ringx.rotation.x = target.rotation.x
|
||
|
ringy.rotation.y = target.rotation.y
|
||
|
ringz.rotation.z = target.rotation.z
|
||
|
|
||
|
// control.dispatchEvent({ type: 'change' })
|
||
|
})
|
||
|
|
||
|
const targetChangedAction = () => {
|
||
|
setTarget(target.position, target.rotation)
|
||
|
|
||
|
// bonus points: how to not fire an action from this reducer and still be able
|
||
|
// to call CHANGE_TARGET on Target and sync the ROBOT_TARGET
|
||
|
// state.dispatch('ROBOT_CHANGE_TARGET', {
|
||
|
// position: target.position,
|
||
|
// rotation: target.rotation,
|
||
|
// })
|
||
|
}
|
||
|
|
||
|
// control.rotation.x = 2
|
||
|
control.addEventListener('change', () => {
|
||
|
if (!disableUpdate) { // changing controlmode causes a loop
|
||
|
targetChangedAction()
|
||
|
}
|
||
|
})
|
||
|
control.attach(target)
|
||
|
|
||
|
scene.add(control)
|
||
|
|
||
|
eulerRings.visible = store.getState().eulerRingsVisible
|
||
|
control.visible = store.getState().controlVisible
|
||
|
|
||
|
window.addEventListener('keydown', (event) => {
|
||
|
switch (event.keyCode) {
|
||
|
case 82:
|
||
|
console.log('rotation mode')
|
||
|
setMode('rotate')
|
||
|
break
|
||
|
case 84:
|
||
|
console.log('translation mode')
|
||
|
setMode('translate')
|
||
|
break
|
||
|
default:
|
||
|
break
|
||
|
}
|
||
|
}, false)
|
||
|
|
||
|
store.action('CONTROL_SPACE_TOGGLE', state => state.controlSpace, controlSpace => ((controlSpace === 'local') ? 'world' : 'local'))
|
||
|
|
||
|
function toggleSpace() {
|
||
|
store.dispatch('CONTROL_SPACE_TOGGLE')
|
||
|
}
|
||
|
|
||
|
function getState() {
|
||
|
return store.getState()
|
||
|
}
|
||
|
|
||
|
// TODO change this to match the state first API
|
||
|
|
||
|
function setMode(mode) {
|
||
|
store.dispatch('CHANGE_CONTROL_MODE', mode)
|
||
|
}
|
||
|
|
||
|
store.action('CHANGE_CONTROL_MODE', (state, data) => ({ ...state,
|
||
|
controlMode: data,
|
||
|
}))
|
||
|
|
||
|
store.action('TARGET_CHANGE_TARGET', (state, data) => {
|
||
|
// + this function can be called from outside
|
||
|
// + may otherwise lead to inconsistent state, where followTarget: true, but pos of target and robot do not match (or not?, listen() will always be consistent)
|
||
|
// - action should only care about its own state
|
||
|
// - can lead to loop
|
||
|
// - need only one way to do it, UI may only need to update other modules state, so only update others sate is needed
|
||
|
// const pos = { ...state.rotation,
|
||
|
// ...data.rotation,
|
||
|
// }
|
||
|
//
|
||
|
// console.log(pos)
|
||
|
if (state.followTarget) {
|
||
|
store.getStore('Robot').dispatch('ROBOT_CHANGE_TARGET', {
|
||
|
position: { ...state.position, // allow for changing only one parameter (x,y,z)
|
||
|
...data.position,
|
||
|
},
|
||
|
rotation: { ...state.rotation,
|
||
|
...data.rotation,
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
return { ...state,
|
||
|
position: { ...state.position, // allow for changing only one parameter (x,y,z)
|
||
|
...data.position,
|
||
|
},
|
||
|
rotation: { ...state.rotation,
|
||
|
...data.rotation,
|
||
|
},
|
||
|
}
|
||
|
})
|
||
|
|
||
|
function setTarget(position, rotation) {
|
||
|
store.dispatch('TARGET_CHANGE_TARGET', {
|
||
|
position: {
|
||
|
x: position.x,
|
||
|
y: position.y,
|
||
|
z: position.z,
|
||
|
},
|
||
|
rotation: {
|
||
|
x: rotation.x,
|
||
|
y: rotation.y,
|
||
|
z: rotation.z,
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
module.exports = store
|
||
|
})
|