diff --git a/index.html b/index.html index da6d15622..e5f54abed 100644 --- a/index.html +++ b/index.html @@ -88,7 +88,7 @@ - + @@ -137,7 +137,7 @@ sceneEl.components.inspector.openInspector(); document.querySelector('.viewer-header-wrapper').style.display = 'none'; } - + AFRAME.registerComponent('timed-inspector', { init: function() { setTimeout( function () { diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 204ed00a5..877136ade 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -196,6 +196,7 @@ function getZPositions (start, end, step) { function createSidewalkClonedVariants (segmentWidthInMeters, density, elevationPosY = 0, streetLength, direction = 'random', animated = false) { const xValueRange = [-(0.37 * segmentWidthInMeters), (0.37 * segmentWidthInMeters)]; const zValueRange = getZPositions((-0.5 * streetLength), (0.5 * streetLength), 1.5); + const densityFactors = { empty: 0, sparse: 0.03, @@ -207,7 +208,7 @@ function createSidewalkClonedVariants (segmentWidthInMeters, density, elevationP dividerParentEl.setAttribute('position', { y: elevationPosY }); // Randomly generate avatars for (let i = 0; i < totalPedestrianNumber; i++) { - const variantName = (animated === true) ? 'a_char' + String(getRandomIntInclusive(1, 8)) : 'char' + String(getRandomIntInclusive(1, 16)); + const variantName = 'a_char' + String(getRandomIntInclusive(1, 8)); const xVal = getRandomArbitrary(xValueRange[0], xValueRange[1]); const zVal = zValueRange.pop(); const yVal = 0; @@ -216,14 +217,21 @@ function createSidewalkClonedVariants (segmentWidthInMeters, density, elevationP let animationDirection = 'inbound'; placedObjectEl.setAttribute('position', { x: xVal, y: yVal, z: zVal }); placedObjectEl.setAttribute('mixin', variantName); + placedObjectEl.setAttribute('position', { x: xVal, y: yVal, z: zVal }); // Roughly 50% of traffic will be incoming if (Math.random() < 0.5 && direction === 'random') { placedObjectEl.setAttribute('rotation', '0 180 0'); animationDirection = 'outbound'; } - if (animated) { - addLinearStreetAnimation(placedObjectEl, 1.4, streetLength, xVal, yVal, zVal, animationDirection); + if (animated) { + placedObjectEl.setAttribute('automation-element', { + zPos: zVal, + direction: animationDirection, + speed: 1.4, + streetLength: streetLength, + mixer: true + }); } dividerParentEl.append(placedObjectEl); } @@ -313,37 +321,6 @@ function createBusElement (variantList, length, showVehicles) { return busParentEl; } -function addLinearStreetAnimation (reusableObjectEl, speed, streetLength, xPos, yVal = 0, zPos, direction) { - const totalStreetDuration = (streetLength / speed) * 1000; // time in milliseconds - const halfStreet = (direction === 'outbound') - ? -streetLength / 2 - : streetLength / 2; - const startingDistanceToTravel = Math.abs(halfStreet - zPos); - const startingDuration = (startingDistanceToTravel / speed) * 1000; - - const animationAttrs_1 = { - property: 'position', - easing: 'linear', - loop: 'false', - from: { x: xPos, y: yVal, z: zPos }, - to: { z: halfStreet }, - dur: startingDuration - }; - const animationAttrs_2 = { - property: 'position', - easing: 'linear', - loop: 'true', - from: { x: xPos, y: yVal, z: -halfStreet }, - to: { x: xPos, y: yVal, z: halfStreet }, - delay: startingDuration, - dur: totalStreetDuration - }; - reusableObjectEl.setAttribute('animation__1', animationAttrs_1); - reusableObjectEl.setAttribute('animation__2', animationAttrs_2); - - return reusableObjectEl; -} - function createDriveLaneElement (variantList, segmentWidthInMeters, streetLength, animated = false, showVehicles = true, count = 1, carStep = undefined) { if (!showVehicles) { return; @@ -373,11 +350,7 @@ function createDriveLaneElement (variantList, segmentWidthInMeters, streetLength } else { rotationY = rotationVariants[lineVariant]; } - /* - if (carType === 'pedestrian') { - return createSidewalkClonedVariants(segmentWidthInMeters, 'normal', streetLength, lineVariant, animated); - } -*/ + const driveLaneParentEl = document.createElement('a-entity'); if (variantList.length == 1) { @@ -430,7 +403,12 @@ function createDriveLaneElement (variantList, segmentWidthInMeters, streetLength reusableObjectEl.setAttribute('wheel', { speed: speed, wheelDiameter: params['wheelDiameter'] } ); - addLinearStreetAnimation(reusableObjectEl, speed, streetLength, 0, 0, positionZ, direction); + reusableObjectEl.setAttribute('automation-element', { + zPos: positionZ, + direction: direction, + speed: speed, + streetLength: streetLength + }); } driveLaneParentEl.append(reusableObjectEl); return reusableObjectEl; @@ -554,7 +532,14 @@ function createMicroMobilityElement (variantList, segmentType, posY = 0, length, if (animated) { reusableObjectEl.setAttribute('animation-mixer', ''); const speed = 5; - addLinearStreetAnimation(reusableObjectEl, speed, length, 0, posY, randPosZ, variantList[0]); + reusableObjectEl.setAttribute('automation-element', { + zPos: randPosZ, + direction: variantList[0], + speed: speed, + streetLength: length, + mixer: true + }); + } if (segmentType === 'bike-lane') { mixinId = cyclistMixins[getRandomIntInclusive(0, countCyclist)]; diff --git a/src/components/automation.js b/src/components/automation.js new file mode 100644 index 000000000..c5df3499e --- /dev/null +++ b/src/components/automation.js @@ -0,0 +1,109 @@ +/* global AFRAME */ +/* +The automation-element component controls all animation of the element +*/ +AFRAME.registerComponent('automation-element', { + schema: { + // initial z position of element + zPos: { type: 'number', default: 0 }, + direction: { type: 'string', default: 'outbound', oneOf: ['outbound', 'inbound'] }, + enabled: { type: 'boolean', default: true }, + speed: { type: 'number', default: 1000 }, + streetLength: { type: 'number', default: 60 } + }, + init: function () { + const el = this.el; + this.addLinearAnimation(); + // save position after pause animation (switch to Editor mode) + let animPausePos = new THREE.Vector3; + // flag to skip initial animation play + let firstPlayFlag = true; + + el.addEventListener('play', (evt) => { + if (!firstPlayFlag && !el.object3D.position.equals(animPausePos)) { + // the object's position has been changed in the Editor. Update animation + this.addLinearAnimation(); + } + firstPlayFlag = false; + }); + el.addEventListener('pause', () => { + // save position while animation pause (switch to the Editor mode) + const pos = el.object3D.position; + animPausePos.copy(pos); + }); + el.addEventListener('animationcomplete', () => { + // move the object to the beginning of the path + let pos = el.object3D.position; + pos.z = -pos.z; + el.setAttribute('position', pos); + el.removeAttribute('animation'); + // change animtaion settings + this.addLinearAnimation(); + }); + }, + addLinearAnimation: function () { + const el = this.el; + const streetLength = this.data.streetLength; + const speed = this.data.speed; + const direction = this.data.direction; + const zPos = el.object3D.position.z; + //const zPos = this.data.zPos; + + const totalStreetDuration = (streetLength / speed) * 1000; // time in milliseconds + + if (direction === 'outbound') { + halfStreet = -streetLength / 2; + el.setAttribute('rotation', {y: 180}); + } else { + halfStreet = streetLength / 2; + el.setAttribute('rotation', {y: 0}); + } + const startingDistanceToTravel = Math.abs(halfStreet - zPos); + const startingDuration = (startingDistanceToTravel / speed) * 1000; + + // animation params to move an object from its current position to the end of street + // in a specified direction + const animationAttrs_1 = { + property: 'object3D.position.z', + easing: 'linear', + loop: 'false', + from: zPos, + to: halfStreet, + dur: startingDuration + }; + + el.setAttribute('animation', animationAttrs_1); + }, + toggleAnimation: function (enabled) { + const el = this.el; + const elemComponents = el.components; + if (elemComponents['animation']) { + // toggle animations that bypass play/pause events that are called by the aframe-inspector + elemComponents['animation'].initialized = enabled; + }; + + if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 1 * enabled}); + + if (elemComponents['wheel']) { + elemComponents['wheel'].data.isActive = enabled; + } + }, + update: function (oldData) { + // If `oldData` is empty, then this means we're in the initialization process. + // No need to update. + if (Object.keys(oldData).length === 0) { return; } + + const changedData = AFRAME.utils.diff(this.data, oldData); + const changedKeysNumber = Object.keys(changedData).length; + + if (changedData.hasOwnProperty('enabled')) { + this.toggleAnimation(changedData.enabled); + // if only 'enabled' data changed + if (changedKeysNumber == 1) return; + } + + if (changedKeysNumber > 0) { + this.addLinearAnimation(); + } + } +}); diff --git a/src/index.js b/src/index.js index e1b51f153..c7262ebc1 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ require('./assets.js'); require('./components/notify.js'); require('./components/create-from-json'); require('./components/screentock.js'); +require('./components/automation.js'); require('aframe-atlas-uvs-component'); AFRAME.registerComponent('street', { @@ -22,11 +23,28 @@ AFRAME.registerComponent('street', { showGround: { default: true }, showStriping: { default: true }, showVehicles: { default: true }, - globalAnimated: { default: false }, + globalAnimated: { default: true }, length: { default: 60 } // new default of 60 from 0.4.4 }, + init: function () { + this.asceneElem = document.querySelector('a-scene'); + }, + toggleGlobalAnimation: function (globalAnimated) { + const animatedElements = document.querySelectorAll('a-entity[automation-element]'); + animatedElements.forEach(animatedElem => { + animatedElem.setAttribute('automation-element', `enabled: ${globalAnimated}`); + }); + }, update: function (oldData) { // fired once at start and at each subsequent change of a schema value - var data = this.data; + const data = this.data; + + const changedData = AFRAME.utils.diff(data, oldData); + + if (Object.keys(changedData).length == 1 && changedData.hasOwnProperty('globalAnimated')) { + // if only globalAnimated option is changed + this.toggleGlobalAnimation(data.globalAnimated); + return; + } if (data.JSON.length === 0) { if (oldData.JSON !== undefined && oldData.JSON.length === 0) { return; } // this has happened before, surpress console log @@ -498,7 +516,8 @@ AFRAME.registerComponent('street-environment', { AFRAME.registerComponent('wheel', { schema: { speed: { type: 'number', default: 1 }, - wheelDiameter: { type: 'number', default: 1 } + wheelDiameter: { type: 'number', default: 1 }, + isActive: { type: 'boolean', default: true} }, init: function () { @@ -521,6 +540,8 @@ AFRAME.registerComponent('wheel', { }); }, tick: function (t, dt) { + if (!this.data.isActive) return; + const speed = this.data.speed / 1000; const wheelDiameter = this.data.wheelDiameter;