diff --git a/index.html b/index.html index 1b8316e..0f9caae 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,9 @@ diff --git a/src/app/grid-coord.js b/src/app/grid-coord.js new file mode 100644 index 0000000..b0d9ff4 --- /dev/null +++ b/src/app/grid-coord.js @@ -0,0 +1,48 @@ +/* global AFRAME */ +/** + * change position of element by it's grid coordinates + * + */ +AFRAME.registerComponent('grid-coord', { + schema: { + gridCoord: {type: 'string', default: ''}, + gridSize: {type: 'number', default: 5}, + gridDivisions: {type: 'number', default: 20} + }, + init: function () { + const data = this.data; + const el = this.el; + + if (!data.gridCoord) { return; } + + [this.lon, this.lat] = data.gridCoord.split(','); + + const gridPos = this.stateToLocalGrid(data.gridSize, data.gridDivisions); + + el.setAttribute('position', gridPos); + + }, + // Convert long / lat state grid coordinates to local grid position + stateToLocalGrid: function (gridSize, gridDivisions) { + // grid divisions (# of cells) ie 20 divisions + // grid size (meter) ie 5 meters + // cellsPerMeter: gridDivisions / gridSize = 4 cells per meter + const cellsPerMeter = gridDivisions / gridSize; + const snap = this.el.getAttribute('snap'); + let snapOffset = (snap) ? snap['offset'] : 0.01; + + const [posLong, posLat] = + [this.lon, this.lat].map((strVal) => { + const numVal = Number(strVal); + // add - or + snap offset is used to snap element to the desired grid cell + return (numVal + (numVal >= 0 ? -snapOffset : snapOffset )) / cellsPerMeter; + }); + // console.log(`Lat Y: ${posLat}, Long X: ${posLong}`); + + const localPos = { + x: posLong, + z: posLat * -1 + }; + return localPos; + } +}); \ No newline at end of file diff --git a/src/app/grid-model.js b/src/app/grid-model.js new file mode 100644 index 0000000..0a3f169 --- /dev/null +++ b/src/app/grid-model.js @@ -0,0 +1,25 @@ +/* global AFRAME */ +/** + * change gltf-model of element by model name + * + */ +AFRAME.registerComponent('grid-model', { + schema: { + model: {type: 'string', default: ''} + }, + addGltfModel: function (modelName) { + const el = this.el; + const sceneEl = el.sceneEl; + + if (sceneEl.catalogIsloaded) { + el.setAttribute('gltf-model', './' + sceneEl.systems.state.state.model.list[modelName].dist); + } else { + sceneEl.addEventListener('catalogIsLoaded', () => { + el.setAttribute('gltf-model', './' + sceneEl.systems.state.state.model.list[modelName].dist); + }) + } + }, + update: function(oldData) { + this.addGltfModel(this.data.model); + } +}); diff --git a/src/app/grid-rotation.js b/src/app/grid-rotation.js new file mode 100644 index 0000000..490ce2b --- /dev/null +++ b/src/app/grid-rotation.js @@ -0,0 +1,16 @@ +/* global AFRAME */ +/** + * change rotation of element by rotation value of y-axis + * + */ +AFRAME.registerComponent('grid-rotation', { + schema: { + rotation: {type: 'number', default: 0} + }, + update: function(oldData) { + const data = this.data; + const el = this.el; + + el.setAttribute('rotation', {x:0, y: data.rotation, z: 0}); + } +}); \ No newline at end of file diff --git a/src/app/intersection-spawn.js b/src/app/intersection-spawn.js index 45b2af4..adbe5ba 100644 --- a/src/app/intersection-spawn.js +++ b/src/app/intersection-spawn.js @@ -31,7 +31,6 @@ AFRAME.registerComponent('intersection-spawn', { const localPos = _worldToLocal(evt.detail.intersection.point, targetEl); // convert world intersection position to local position const gridPos = this.localToStateGrid(localPos, data.gridSize, data.gridDivisions); - // console.log('Grid Position:', gridPos); AFRAME.scenes[0].emit('addGridObject', { lon: gridPos.x, diff --git a/src/app/load-model.js b/src/app/load-model.js deleted file mode 100644 index 53b2eec..0000000 --- a/src/app/load-model.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global AFRAME */ -/** - * add model to scene ground by its name and grid coordinats. - * - * `` will spawn - * `` at intersection point. - */ -AFRAME.registerComponent('add-model', { - schema: { - name: {type: 'string', default: ''}, - gridCoord: {type: 'array', default: []}, - //lat: {type: 'number', default: ''}, - gridSize: {type: 'number', default: 5}, - gridDivisions: {type: 'number', default: 20} - }, - - init: function () { - const data = this.data; - const el = this.el; - const sceneEl = el.sceneEl; - this.helperVector = new THREE.Vector3(); - - if (!data.name && data.gridCoord) { return; } - - const gridPos = this.stateToLocalGrid(data.gridCoord, data.gridSize, data.gridDivisions); - - el.setAttribute('position', gridPos); - - if (sceneEl.catalogIsloaded) { - el.setAttribute('gltf-model', './' + sceneEl.systems.state.state.model.list[data.name].dist); - } else { - sceneEl.addEventListener('catalogIsLoaded', () => { - el.setAttribute('gltf-model', './' + sceneEl.systems.state.state.model.list[data.name].dist); - }) - } - }, - // Convert long / lat state grid coordinates to local grid position - stateToLocalGrid: function (lon_lat, gridSize, gridDivisions) { - // grid divisions (# of cells) ie 20 divisions - // grid size (meter) ie 5 meters - // cellsPerMeter: gridDivisions / gridSize = 4 cells per meter - const cellsPerMeter = gridDivisions / gridSize; - const snap = this.el.getAttribute('snap'); - let snapOffset = (snap) ? snap['offset'] : 0.01; - - const [posLong, posLat] = - lon_lat.map((strVal) => { - const numVal = Number(strVal); - // add - or + snap offset is used to snap element to the desired grid cell - return (numVal + (numVal >= 0 ? -snapOffset : snapOffset )) / cellsPerMeter; - }); - // console.log(`Lat Y: ${posLat}, Long X: ${posLong}`); - - const localPos = { - x: posLong, - z: posLat * -1 - }; - return localPos; - } -}); \ No newline at end of file diff --git a/src/app/state.js b/src/app/state.js index b5f4a91..e0628ed 100644 --- a/src/app/state.js +++ b/src/app/state.js @@ -36,11 +36,14 @@ const GRID_SIZE = 10; // Example grid size (10x10) // Initial grid state setup with City Hall at the center const INITIAL_GRID_STATE = []; + const INIT_GRID_COORDS = [[-1, -1], [-1, 1], [1, -1], [1, 1]]; -INIT_GRID_COORDS.forEach(lon_lat_values => { +INIT_GRID_COORDS.forEach(coord => { + const lon = coord[0]; + const lat = coord[1]; INITIAL_GRID_STATE.push( - {coord: lon_lat_values, model: 'park_base', rotation: 0, elevation: 0} + {coord: coord.join(','), model: 'park_base', rotation: 0, elevation: 0} ); }); @@ -103,7 +106,10 @@ AFRAME.registerState({ let { lon, lat, model, rotation, elevation } = payload; - if (isCityHall(lon, lat)) return; + if (isCityHall(lon, lat)) { + console.log('Cannot place object on city hall'); + return; + } if (!model) { model = getModelNameFromState(state); @@ -117,39 +123,56 @@ AFRAME.registerState({ console.log('model', model); const keyCell = findGridIndex(state.grid, lon, lat); - // Prevent modification of City Hall cells and exists cells if (!keyCell) { + // If the cell is free add selected model to the spawned cell state.grid.push( - { coord: [lon, lat], model: model, rotation: rotation, elevation: elevation } + { coord: [lon, lat].join(','), model: model, rotation: rotation, elevation: elevation } ); } else { - console.log('Cannot place object on city hall'); + + if (state.grid[keyCell].model === model) { + // if the cell model is the same as the selected model + this.rotateOrElevateGridObject(state, {keyCell, addRotation: 90}); + } else { + // change the current model of element to selected model + // Setting the __dirty flag is needed to track changes + // in the value of individual elements, but not the entire array + state.grid.__dirty = true; + state.grid[keyCell].model = model; + state.grid[keyCell].rotation = 0; + } } console.log('Updated Grid State:', state.grid); }, // Handler to rotate or elevate an object // Note: City Hall cells cannot be rotated or elevated rotateOrElevateGridObject: function (state, payload) { - const { lon, lat, rotation, elevation } = payload; + let { lon, lat, addRotation, addElevation, keyCell } = payload; if (isCityHall(lon, lat)) return; - const keyCell = findGridIndex(state.grid, lon, lat); + keyCell = keyCell ?? findGridIndex(state.grid, lon, lat); - if (keyCell) { - - state.grid[keyCell].rotation += rotation; - state.grid[keyCell].elevation += elevation; + if (addRotation) { + // Setting the __dirty flag is needed to track changes + // in the value of individual elements, but not the entire array + state.grid.__dirty = true; + const currentRot = state.grid[keyCell].rotation; + // prevent big rotation values + state.grid[keyCell].rotation = (currentRot + addRotation) % 360; } + + if (addElevation) state.grid[keyCell].elevation += addElevation; + }, // Handler to clear a cell // Note: City Hall cells cannot be cleared clearGridCell: function (state, payload) { - const { lon, lat } = payload; + const { lon, lat, keyCell } = payload; if (isCityHall(lon, lat)) return; - const keyCell = findGridIndex(state.grid, lon, lat); + keyCell = keyCell ?? findGridIndex(state.grid, lon, lat); if (keyCell) { state.grid.splice(keyCell, 1); @@ -168,16 +191,21 @@ AFRAME.registerState({ // check for City Hall cells function isCityHall(lon, lat) { + if (! lon && lat) return false; + const cityHallCell = INIT_GRID_COORDS.findIndex( - (elData) => elData[0] == lon && elData[1] == lat + (coord) => coord[0] == lon && coord[1] == lat ); return (cityHallCell == -1) ? false: true; } // find index of grid array element by grid coordinates function findGridIndex(gridArray, lon, lat) { + if (! lon && lat) return; + const strCoord = [lon, lat].join(','); + const gridArrayIndex = gridArray.findIndex( - (elData) => elData.coord[0] == lon && elData.coord[1] == lat + (elData) => elData.coord == strCoord ); return gridArrayIndex === -1 ? null : gridArrayIndex; } @@ -225,6 +253,7 @@ AFRAME.registerComponent('load-catalog', { } this.el.catalogIsloaded = true; this.el.emit('catalogIsLoaded'); + } }); diff --git a/src/index.js b/src/index.js index 788c23a..8a982db 100644 --- a/src/index.js +++ b/src/index.js @@ -12,4 +12,6 @@ require('./app/intersection-spawn.js'); require('./app/snap.js'); require('./app/set-rotation-from-anchor.js'); require('./app/state.js'); -require('./app/load-model.js'); \ No newline at end of file +require('./app/grid-coord.js'); +require('./app/grid-model.js'); +require('./app/grid-rotation.js'); \ No newline at end of file