From 5be0d4c937b10b0f9e7b2157a0abe2575f262ded Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Thu, 14 Dec 2023 22:42:38 -0300 Subject: [PATCH 1/8] toggle global animation in live mode --- src/index.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index cb3710110..b8bdc76b3 100644 --- a/src/index.js +++ b/src/index.js @@ -25,9 +25,38 @@ AFRAME.registerComponent('street', { globalAnimated: { default: false }, length: { default: 60 } // new default of 60 from 0.4.4 }, + toggleGlobalAnimation: function () { + const globalAnimated = this.data.globalAnimated; + const animatedElements = document.querySelectorAll('a-entity[animation__1]'); + animatedElements.forEach(animatedElem => { + // hook to toggle animations that bypass play/pause events that are called by the inspector + animatedElem.components.animation__1.initialized = globalAnimated; + }); + + // toggle animations for elements with animation-mixer component + const animationMixerElements = document.querySelectorAll('a-entity[animation-mixer]'); + animationMixerElements.forEach(animatedElem => { + if (globalAnimated) { + animatedElem.components['animation-mixer'].playAction(); + } else { + animatedElem.components['animation-mixer'].stopAction(); + } + }); + + // toggle wheel animation for all elements with wheel component + const wheelElements = document.querySelectorAll('a-entity[wheel]'); + wheelElements.forEach(wheelElem => { + wheelElem.components['wheel'].data.isActive = globalAnimated; + }); + }, update: function (oldData) { // fired once at start and at each subsequent change of a schema value var data = this.data; + if (data.globalAnimated !== oldData.globalAnimated && oldData.JSON === data.JSON) { + this.toggleGlobalAnimation(); + return; + } + if (data.JSON.length === 0) { if (oldData.JSON !== undefined && oldData.JSON.length === 0) { return; } // this has happened before, surpress console log console.log('[street]', 'No JSON provided yet, but it might be set at runtime'); @@ -486,7 +515,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 () { @@ -509,6 +539,8 @@ AFRAME.registerComponent('wheel', { }); }, tick: function (t, dt) { + if (!this.data.isActive) return; + const speed = this.data.speed / 1000; const wheelDiameter = this.data.wheelDiameter; From 13d3e205d234dd231242aae576a5b123452da35f Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Tue, 6 Feb 2024 14:20:26 -0300 Subject: [PATCH 2/8] fix live toggle globalAnimation --- src/index.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index b8bdc76b3..e270c02bd 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,24 +23,34 @@ 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 }, - toggleGlobalAnimation: function () { - const globalAnimated = this.data.globalAnimated; - const animatedElements = document.querySelectorAll('a-entity[animation__1]'); + init: function () { + this.asceneElem = document.querySelector('a-scene'); + }, + toggleGlobalAnimation: function (globalAnimated) { + const animatedElements = document.querySelectorAll('a-entity[animation__1], a-entity[animation__2]'); animatedElements.forEach(animatedElem => { + const elemComponents = animatedElem.components; // hook to toggle animations that bypass play/pause events that are called by the inspector - animatedElem.components.animation__1.initialized = globalAnimated; + if (elemComponents['animation__1']) { + elemComponents['animation__1'].initialized = globalAnimated; + }; + if (elemComponents['animation__2']) { + elemComponents['animation__2'].initialized = globalAnimated; + } }); // toggle animations for elements with animation-mixer component const animationMixerElements = document.querySelectorAll('a-entity[animation-mixer]'); animationMixerElements.forEach(animatedElem => { if (globalAnimated) { - animatedElem.components['animation-mixer'].playAction(); + // use solution for play/pause animation-mixer from donmccurdy + animatedElem.setAttribute('animation-mixer', {timeScale: 1}); } else { - animatedElem.components['animation-mixer'].stopAction(); + // pause animation-mixer + animatedElem.setAttribute('animation-mixer', {timeScale: 0}); } }); @@ -50,10 +61,13 @@ AFRAME.registerComponent('street', { }); }, 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 (data.globalAnimated !== oldData.globalAnimated && oldData.JSON === data.JSON) { - this.toggleGlobalAnimation(); + if (Object.keys(changedData).length == 1 && changedData.hasOwnProperty('globalAnimated')) { + // if only globalAnimated option is changed + this.toggleGlobalAnimation(data.globalAnimated); return; } From 9e67252ec68869d643f273b27b92d2238fa8992f Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Tue, 6 Feb 2024 14:25:22 -0300 Subject: [PATCH 3/8] remove require automation.js --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index e270c02bd..cabcee910 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,6 @@ 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', { From b75d6f55ac42366b74f57be91a2018c959c75a8d Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Tue, 6 Feb 2024 18:10:59 -0300 Subject: [PATCH 4/8] add automation-element component remove static chars --- index.html | 4 +- src/aframe-streetmix-parsers.js | 58 ++++++++---------------- src/components/automation.js | 79 +++++++++++++++++++++++++++++++++ src/index.js | 1 + 4 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 src/components/automation.js 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 39ab67d69..36d7423e0 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; @@ -215,6 +216,7 @@ function createSidewalkClonedVariants (segmentWidthInMeters, density, elevationP const placedObjectEl = document.createElement('a-entity'); let animationDirection = 'inbound'; 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'); @@ -223,8 +225,14 @@ function createSidewalkClonedVariants (segmentWidthInMeters, density, elevationP placedObjectEl.setAttribute('rotation', '0 0 0'); } - 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); } @@ -314,37 +322,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; @@ -374,11 +351,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) { @@ -431,7 +404,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; diff --git a/src/components/automation.js b/src/components/automation.js new file mode 100644 index 000000000..ad1e1dfd4 --- /dev/null +++ b/src/components/automation.js @@ -0,0 +1,79 @@ +/* global AFRAME */ + +/* +The animation-element component controls all animation of the elements +*/ +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(); + }, + 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 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 + }; + // Animation parameters for the next animation cycle. + // They can be changed when changing position of the object in the editor + const animationAttrs_2 = { + property: 'object3D.position.z', + autoplay: false, + easing: 'linear', + loop: 'true', + from: -halfStreet, + to: halfStreet, + dur: totalStreetDuration, + startEvents: 'animationcomplete__1' + //startEvents: 'startAnim2' + }; + el.setAttribute('animation__1', animationAttrs_1); + el.setAttribute('animation__2', animationAttrs_2); + }, + animationCompleteEvent: function (evt) { + const elem = evt.target; + //this.el.parentEl.emit('addInBuffer', {uuid: elem.uuid}); + }, + 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); + + if (Object.keys(changedData).length > 0) { + this.addLinearAnimation(); + } + } +}); diff --git a/src/index.js b/src/index.js index cabcee910..e270c02bd 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', { From 7712dc2a62cdf74075e21e4d6544ea6b784435e1 Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Wed, 7 Feb 2024 19:50:19 -0300 Subject: [PATCH 5/8] toggle animation of one element --- src/components/automation.js | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/automation.js b/src/components/automation.js index ad1e1dfd4..429c79d3b 100644 --- a/src/components/automation.js +++ b/src/components/automation.js @@ -1,7 +1,7 @@ /* global AFRAME */ /* -The animation-element component controls all animation of the elements +The automation-element component controls all animation of the element */ AFRAME.registerComponent('automation-element', { schema: { @@ -56,14 +56,33 @@ AFRAME.registerComponent('automation-element', { to: halfStreet, dur: totalStreetDuration, startEvents: 'animationcomplete__1' - //startEvents: 'startAnim2' }; el.setAttribute('animation__1', animationAttrs_1); el.setAttribute('animation__2', animationAttrs_2); }, animationCompleteEvent: function (evt) { const elem = evt.target; - //this.el.parentEl.emit('addInBuffer', {uuid: elem.uuid}); + }, + toggleAnimation: function (enabled) { + const el = this.el; + const elemComponents = el.components; + if (elemComponents['animation__1']) { + // toggle animations that bypass play/pause events that are called by the aframe-inspector + elemComponents['animation__1'].initialized = enabled; + }; + if (elemComponents['animation__2']) { + elemComponents['animation__2'].initialized = enabled; + } + if (enabled) { + // enable animation-mixer + if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 1}); + } else { + // disable animation-mixer + if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 0}); + } + if (elemComponents['wheel']) { + elemComponents['wheel'].data.isActive = enabled; + } }, update: function (oldData) { // If `oldData` is empty, then this means we're in the initialization process. @@ -71,8 +90,15 @@ AFRAME.registerComponent('automation-element', { 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 (Object.keys(changedData).length > 0) { + if (changedKeysNumber > 0) { this.addLinearAnimation(); } } From 0b20b1fa52d4eff500a34ae4cca232ea6b964d14 Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Wed, 7 Feb 2024 19:50:44 -0300 Subject: [PATCH 6/8] change toggle globalAnimated method --- src/index.js | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index e270c02bd..215f6f4e4 100644 --- a/src/index.js +++ b/src/index.js @@ -30,35 +30,10 @@ AFRAME.registerComponent('street', { this.asceneElem = document.querySelector('a-scene'); }, toggleGlobalAnimation: function (globalAnimated) { - const animatedElements = document.querySelectorAll('a-entity[animation__1], a-entity[animation__2]'); + const animatedElements = document.querySelectorAll('a-entity[automation-element]'); animatedElements.forEach(animatedElem => { - const elemComponents = animatedElem.components; - // hook to toggle animations that bypass play/pause events that are called by the inspector - if (elemComponents['animation__1']) { - elemComponents['animation__1'].initialized = globalAnimated; - }; - if (elemComponents['animation__2']) { - elemComponents['animation__2'].initialized = globalAnimated; - } - }); - - // toggle animations for elements with animation-mixer component - const animationMixerElements = document.querySelectorAll('a-entity[animation-mixer]'); - animationMixerElements.forEach(animatedElem => { - if (globalAnimated) { - // use solution for play/pause animation-mixer from donmccurdy - animatedElem.setAttribute('animation-mixer', {timeScale: 1}); - } else { - // pause animation-mixer - animatedElem.setAttribute('animation-mixer', {timeScale: 0}); - } + animatedElem.setAttribute('automation-element', `enabled: ${globalAnimated}`); }); - - // toggle wheel animation for all elements with wheel component - const wheelElements = document.querySelectorAll('a-entity[wheel]'); - wheelElements.forEach(wheelElem => { - wheelElem.components['wheel'].data.isActive = globalAnimated; - }); }, update: function (oldData) { // fired once at start and at each subsequent change of a schema value const data = this.data; From 7e1dbd69383dc67221a05e6c8830e9fe826a95c7 Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Thu, 8 Feb 2024 16:17:40 -0300 Subject: [PATCH 7/8] replace addLinearAnimation function with new automation component --- src/aframe-streetmix-parsers.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 598c8f27d..877136ade 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -532,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)]; From 0dd9bce8ff246689ec1f1dc0dc9ff332865e601e Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Mon, 12 Feb 2024 20:17:42 -0300 Subject: [PATCH 8/8] fix saving the position change of the animated object --- src/components/automation.js | 64 +++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/components/automation.js b/src/components/automation.js index 429c79d3b..c5df3499e 100644 --- a/src/components/automation.js +++ b/src/components/automation.js @@ -1,5 +1,4 @@ /* global AFRAME */ - /* The automation-element component controls all animation of the element */ @@ -15,6 +14,32 @@ AFRAME.registerComponent('automation-element', { 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; @@ -22,6 +47,7 @@ AFRAME.registerComponent('automation-element', { 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 @@ -45,41 +71,19 @@ AFRAME.registerComponent('automation-element', { to: halfStreet, dur: startingDuration }; - // Animation parameters for the next animation cycle. - // They can be changed when changing position of the object in the editor - const animationAttrs_2 = { - property: 'object3D.position.z', - autoplay: false, - easing: 'linear', - loop: 'true', - from: -halfStreet, - to: halfStreet, - dur: totalStreetDuration, - startEvents: 'animationcomplete__1' - }; - el.setAttribute('animation__1', animationAttrs_1); - el.setAttribute('animation__2', animationAttrs_2); - }, - animationCompleteEvent: function (evt) { - const elem = evt.target; + + el.setAttribute('animation', animationAttrs_1); }, toggleAnimation: function (enabled) { const el = this.el; const elemComponents = el.components; - if (elemComponents['animation__1']) { + if (elemComponents['animation']) { // toggle animations that bypass play/pause events that are called by the aframe-inspector - elemComponents['animation__1'].initialized = enabled; + elemComponents['animation'].initialized = enabled; }; - if (elemComponents['animation__2']) { - elemComponents['animation__2'].initialized = enabled; - } - if (enabled) { - // enable animation-mixer - if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 1}); - } else { - // disable animation-mixer - if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 0}); - } + + if (this.data.mixer) el.setAttribute('animation-mixer', {timeScale: 1 * enabled}); + if (elemComponents['wheel']) { elemComponents['wheel'].data.isActive = enabled; }