diff --git a/assets b/assets index 25645ccd4..f724477b2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 25645ccd421a7e50682b0e5b4ebe3d22f667dcb3 +Subproject commit f724477b28e0867ca3dee08d1d6b11c2aafc2add diff --git a/index.html b/index.html index eed1f3516..af89e2629 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ - + @@ -70,6 +70,7 @@ notify metadata scene-title + reflection > diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 2cbf0526f..64fedba8c 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -108,22 +108,22 @@ function createStencilsParentElement (position) { return placedObjectEl; } -function createRailsElement(length, railsPosX) { +function createRailsElement (length, railsPosX) { const placedObjectEl = document.createElement('a-entity'); const railsGeometry = { - "primitive": "box", - "depth": length, - "width": 0.1, - "height": 0.2, + primitive: 'box', + depth: length, + width: 0.1, + height: 0.2 - } + }; const railsMaterial = { // TODO: Add environment map for reflection on metal rails - "color": "#8f8f8f", - "metalness": 1, - "emissive": "#828282", - "emissiveIntensity": 0.5, - "roughness": 0.1 - } + color: '#8f8f8f', + metalness: 1, + emissive: '#828282', + emissiveIntensity: 0.5, + roughness: 0.1 + }; placedObjectEl.setAttribute('geometry', railsGeometry); placedObjectEl.setAttribute('material', railsMaterial); placedObjectEl.setAttribute('class', 'rails'); @@ -131,7 +131,6 @@ function createRailsElement(length, railsPosX) { placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" return placedObjectEl; - } function createTracksParentElement (length, objectMixinId) { @@ -140,9 +139,9 @@ function createTracksParentElement (length, objectMixinId) { placedObjectEl.setAttribute('position', '0 -0.2 0'); // position="1.043 0.100 -3.463" // add rails const railsWidth = { // width as measured from center of rail, so 1/2 actual width - "tram": 0.7175, // standard gauge 1,435 mm - "trolley": 0.5335 // sf cable car rail gauge 1,067 mm - } + tram: 0.7175, // standard gauge 1,435 mm + trolley: 0.5335 // sf cable car rail gauge 1,067 mm + }; const railsPosX = railsWidth[objectMixinId]; placedObjectEl.append(createRailsElement(length, railsPosX)); placedObjectEl.append(createRailsElement(length, -railsPosX)); @@ -411,7 +410,7 @@ function createDriveLaneElement (variantList, segmentWidthInMeters, streetLength mixin: 'self-driving-cruise-car-rig', wheelDiameter: 0.76, length: 5.17, - width: 2 + width: 2 } }; @@ -1083,23 +1082,22 @@ function processSegments (segments, showStriping, length, globalAnimated, showVe segmentParentEl.append(createSegmentElement(segmentWidthInMeters, positionY, groundMixinId, length, repeatCount, elevation)); } else { segmentParentEl.append(createSeparatorElement(positionY, rotationY, groundMixinId, length, repeatCount, elevation)); - } // returns JSON output instead // append the new surfaceElement to the segmentParentEl streetParentEl.append(segmentParentEl); segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); - segmentParentEl.setAttribute('data-layer-name', 'Segment: ' + segments[i].type + ', ' + variantList[0]) + segmentParentEl.setAttribute('data-layer-name', 'Segment: ' + segments[i].type + ', ' + variantList[0]); } // create new brown box to represent ground underneath street - let dirtBox = document.createElement('a-box'); + const dirtBox = document.createElement('a-box'); const xPos = cumulativeWidthInMeters / 2; - console.log('xPos', xPos) - console.log('`${xPos} -1.1 0`', `${xPos} -1.1 0`) + console.log('xPos', xPos); + console.log('`${xPos} -1.1 0`', `${xPos} -1.1 0`); dirtBox.setAttribute('position', `${xPos} -1.1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 - dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 - dirtBox.setAttribute('width', cumulativeWidthInMeters); - dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side + dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 + dirtBox.setAttribute('width', cumulativeWidthInMeters); + dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side dirtBox.setAttribute('material', 'color: #664B00;'); dirtBox.setAttribute('data-layer-name', 'Underground'); streetParentEl.append(dirtBox); @@ -1114,7 +1112,6 @@ function processBuildings (left, right, streetWidth, showGround, length) { buildingElement.classList.add('buildings-parent'); buildingElement.setAttribute('data-layer-name', 'Buildings & Blocks Container'); buildingElement.setAttribute('position', '0 0.2 0'); - const buildingLotWidth = 150; const buildingsArray = [left, right]; // TODO: Sound temporarily disabled @@ -1134,24 +1131,58 @@ function processBuildings (left, right, streetWidth, showGround, length) { return placedObjectEl; } - // possible 'block' type input values: grass, fence, narrow, wide, waterfront, residential, parking-lot + // possible 'block' type input values: grass, fence, narrow, wide, waterfront, residential, parking-lot, (new: archway, wall sp?) buildingsArray.forEach((currentValue, index) => { if (currentValue.length === 0) { return; } // if empty string then skip const side = (index === 0) ? 'left' : 'right'; const sideMultiplier = (side === 'left') ? -1 : 1; - const positionX = ((buildingLotWidth / 2) + (streetWidth / 2)) * sideMultiplier; + const groundPositionX = ((length / 4) + (streetWidth / 2)) * sideMultiplier; + const buildingPositionX = ((150 / 2) + (streetWidth / 2)) * sideMultiplier; + // this is the logic to make the ground box if (showGround) { - // TODO: consider rewriting this using simple box instead of this json plane method - var groundJSONString = JSON.stringify(streetmixParsersTested.createGroundArray(currentValue, length)); + const variantToMaterialMapping = { + grass: 'ground-grass-material', + fence: 'ground-grass-material', + 'parking-lot': 'ground-parking-lot-material', + residential: 'ground-grass-material', + narrow: 'ground-asphalt-material', + wide: 'ground-asphalt-material', + arcade: 'ground-tiled-concrete-material', + 'compound-wall': 'ground-asphalt-material' + } + + if (currentValue === 'waterfront') { + var groundParentEl = document.createElement('a-ocean-box'); + groundParentEl.setAttribute('geometry', + { + primitive: 'box', + depth: length, + width: length / 2, + height: 2, + segmentsHeight: 1, + segmentsDepth: 10, + segmentsWidth: 10 + }); + groundParentEl.setAttribute('position', { y: -3 }); + } else { + var groundParentEl = document.createElement('a-box'); + groundParentEl.setAttribute('depth', length); + groundParentEl.setAttribute('height', 2); + groundParentEl.setAttribute('width', length / 2); + groundParentEl.setAttribute('shadow', ''); + // groundParentEl.setAttribute('material', 'src:#grass-texture;repeat:5 5;roughness:0.8;'); + groundParentEl.setAttribute('mixin', variantToMaterialMapping[currentValue]); // case grass, fence + groundParentEl.setAttribute('position', { y: -1 }); + } + - var groundParentEl = document.createElement('a-entity'); - groundParentEl.setAttribute('create-from-json', 'jsonString', groundJSONString); if (side === 'right') { - groundParentEl.setAttribute('position', positionX - 55 + ' 0.2 0'); + // groundParentEl.setAttribute('position', groundPositionX + ' -1 0'); + groundParentEl.setAttribute('position', { x: groundPositionX }); } else { - groundParentEl.setAttribute('position', positionX + 55 + ' 0.2 0'); + groundParentEl.setAttribute('position', { x: groundPositionX }); } groundParentEl.classList.add('ground-' + side); groundParentEl.setAttribute('data-layer-name', 'Ground ' + side + ': ' + currentValue); @@ -1160,7 +1191,7 @@ function processBuildings (left, right, streetWidth, showGround, length) { // make building const buildingPos = { - x: positionX, + x: buildingPositionX, y: 0, z: (index === 1) ? length / 2 : -length / 2 }; @@ -1179,32 +1210,38 @@ function processBuildings (left, right, streetWidth, showGround, length) { case 'arcade': buildingPos.x += sideMultiplier * (-70.5); } - let newBuildings = createBuilding(currentValue, sideMultiplier); + const newBuildings = createBuilding(currentValue, sideMultiplier); newBuildings.setAttribute('data-layer-name', 'Buildings ' + side + ': ' + currentValue); newBuildings.setAttribute('position', buildingPos); buildingElement.append(newBuildings); - - if (currentValue === 'waterfront') { - const objectPositionX = positionX - (sideMultiplier * buildingLotWidth / 2); + if (currentValue === 'waterfront' || currentValue === 'compound-wall') { + const objectPositionX = buildingPositionX - (sideMultiplier * 150 / 2); const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'seawall-parent'); - placedObjectEl.setAttribute('position', objectPositionX + ' 0 0'); // position="1.043 0.100 -3.463" + placedObjectEl.setAttribute('position', {x: objectPositionX, z: 4.5}); // position="1.043 0.100 -3.463" + let rotationCloneY; + if (currentValue === 'compound-wall') { + placedObjectEl.setAttribute('position', {y: 3}); + placedObjectEl.setAttribute('position', {x: objectPositionX + 1.5 * sideMultiplier}); + rotationCloneY = (side === 'left') ? 90 : -90; + } else { + rotationCloneY = (side === 'left') ? -90 : 90; + } placedObjectEl.classList.add('seawall-parent-' + side); buildingElement.appendChild(placedObjectEl); // clone a bunch of seawalls under the parent - const rotationCloneY = (side === 'left') ? -90 : 90; cloneMixinAsChildren({ objectMixinId: 'seawall', parentEl: placedObjectEl, rotation: '0 ' + rotationCloneY + ' 0', step: 15, radius: clonedObjectRadius }); } if (currentValue === 'fence' || currentValue === 'parking-lot') { - const objectPositionX = positionX - (sideMultiplier * buildingLotWidth / 2); + const objectPositionX = buildingPositionX - (sideMultiplier * 150 / 2); // make the parent for all the objects to be cloned const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'fence-parent'); placedObjectEl.setAttribute('position', objectPositionX + ' 0 4.625'); // position="1.043 0.100 -3.463" - placedObjectEl.classList.add('fence-parent-' + positionX); + placedObjectEl.classList.add('fence-parent-' + buildingPositionX); // clone a bunch of fences under the parent const rotationCloneY = (side === 'right') ? -90 : 90; cloneMixinAsChildren({ objectMixinId: 'fence', parentEl: placedObjectEl, rotation: '0 ' + rotationCloneY + ' 0', step: 9.25, radius: clonedObjectRadius }); diff --git a/src/assets.js b/src/assets.js index 77ca5d316..1d1e3e101 100644 --- a/src/assets.js +++ b/src/assets.js @@ -214,13 +214,13 @@ function buildAssetHTML (assetUrl, categories) { - - - - + + + + - - + + `, 'loud-bicycle': ` diff --git a/src/components/ocean-plane.js b/src/components/ocean-plane.js deleted file mode 100644 index 0ac0717d1..000000000 --- a/src/components/ocean-plane.js +++ /dev/null @@ -1,38 +0,0 @@ -/* global AFRAME */ - -// from https://samsunginter.net/a-frame-components/dist/ocean-plane.js -// Requires a build from the latest a-frame master (August 2016, v0.3) - -AFRAME.registerComponent('wobble-normal', { - schema: {}, - tick: function (t) { - if (!this.el.components.material.material.normalMap) return; - this.el.components.material.material.normalMap.offset.x += 0.0001 * Math.sin(t / 10000); - this.el.components.material.material.normalMap.offset.y += 0.0001 * Math.cos(t / 8000); - this.el.components.material.material.normalScale.x = 0.5 + 0.5 * Math.cos(t / 1000); - this.el.components.material.material.normalScale.x = 0.5 + 0.5 * Math.sin(t / 1200); - } -}); - -AFRAME.registerPrimitive('a-ocean-plane', { - defaultComponents: { - geometry: { - primitive: 'plane', - height: 1000, - width: 1000 - }, - rotation: '-90 0 0', - material: { - shader: 'standard', - color: '#8ab39f', - metalness: 1, - roughness: 0.2, - normalMap: 'url(assets/materials/waternormals.jpg)', - normalTextureRepeat: '50 50', - normalTextureOffset: '0 0', - normalScale: '0.5 0.5', - opacity: 0.8 - }, - 'wobble-normal': {} - } -}); diff --git a/src/components/ocean.js b/src/components/ocean.js new file mode 100644 index 000000000..fde243544 --- /dev/null +++ b/src/components/ocean.js @@ -0,0 +1,149 @@ +/* global AFRAME */ + +// from https://samsunginter.net/a-frame-components/dist/ocean-plane.js +// Requires a build from the latest a-frame master (August 2016, v0.3) + +AFRAME.registerComponent('wobble-normal', { + schema: {}, + tick: function (t) { + if (!this.el.components.material.material.normalMap) return; + this.el.components.material.material.normalMap.offset.x += 0.0001 * Math.sin(t / 10000); + this.el.components.material.material.normalMap.offset.y += 0.0001 * Math.cos(t / 8000); + this.el.components.material.material.normalScale.x = 0.5 + 0.5 * Math.cos(t / 1000); + this.el.components.material.material.normalScale.x = 0.5 + 0.5 * Math.sin(t / 1200); + } +}); + +AFRAME.registerComponent('wobble-geometry', { + schema: { + // Wave amplitude and variance. + amplitude: { default: 0.1 }, + amplitudeVariance: { default: 0.3 }, + + // Wave speed and variance. + speed: { default: 0.25 }, + speedVariance: { default: 2 } + }, + play: function () { + const data = this.data; + const geometry = this.geometry = this.el.object3D.children[0].geometry; + + console.log(this.el.object3D.children[0].geometry); + this.waves = []; + const posAttribute = geometry.getAttribute('position'); + console.log(posAttribute); + for (let i = 0; i < posAttribute.count; i++) { + this.waves.push({ + z: posAttribute.getZ(i), + ang: Math.random() * Math.PI * 2, + amp: data.amplitude + Math.random() * data.amplitudeVariance, + speed: (data.speed + Math.random() * data.speedVariance) / 1000 // radians / frame + }); + } + }, + tick: function (t, dt) { + if (!dt) return; + + const posAttribute = this.geometry.getAttribute('position'); + for (let i = 0; i < posAttribute.count; i++) { + const vprops = this.waves[i]; + const value = vprops.z + Math.sin(vprops.ang) * vprops.amp; + posAttribute.setZ(i, value); + vprops.ang += vprops.speed * dt; + } + posAttribute.needsUpdate = true; + } +}); + +AFRAME.registerPrimitive('a-ocean-plane', { + defaultComponents: { + geometry: { + primitive: 'plane', + height: 1000, + width: 1000, + segmentsHeight: 100, + segmentsWidth: 100 + }, + rotation: '-90 0 0', + material: { + shader: 'standard', + color: '#8ab39f', + metalness: 1, + roughness: 0.2, + normalMap: 'url(assets/materials/waternormals.jpg)', + normalTextureRepeat: '50 50', + normalTextureOffset: '0 0', + normalScale: '0.5 0.5', + opacity: 0.8 + }, + 'wobble-normal': {}, + 'wobble-geometry': {} + } +}); + +AFRAME.registerComponent('wobble-geometry-box', { + schema: { + // Wave amplitude and variance. + amplitude: { default: 0.1 }, + amplitudeVariance: { default: 0.3 }, + + // Wave speed and variance. + speed: { default: 0.25 }, + speedVariance: { default: 2 } + }, + play: function () { + const data = this.data; + const geometry = this.geometry = this.el.object3D.children[0].geometry; + + this.waves = []; + const posAttribute = geometry.getAttribute('position'); + for (let i = 0; i < posAttribute.count; i++) { + this.waves.push({ + y: posAttribute.getY(i), + ang: Math.random() * Math.PI * 2, + amp: data.amplitude + Math.random() * data.amplitudeVariance, + speed: (data.speed + Math.random() * data.speedVariance) / 1000 // radians / frame + }); + } + }, + tick: function (t, dt) { + if (!dt) return; + + const posAttribute = this.geometry.getAttribute('position'); + for (let i = 0; i < posAttribute.count; i++) { + const vprops = this.waves[i]; + const value = vprops.y + Math.sin(vprops.ang) * vprops.amp; + posAttribute.setY(i, value); + vprops.ang += vprops.speed * dt; + } + posAttribute.needsUpdate = true; + } +}); + +AFRAME.registerPrimitive('a-ocean-box', { + defaultComponents: { + geometry: { + primitive: 'box', + depth: 100, + width: 100, + height: 4, + segmentsHeight: 10, + segmentsDepth: 100, + segmentsWidth: 100 + }, + rotation: '0 0 0', + material: { + shader: 'standard', + color: '#8ab39f', + metalness: 1, + roughness: 0.2, + normalMap: 'url(https://assets.3dstreet.app/materials/waternormals.jpg)', + normalTextureRepeat: '10 10', + normalTextureOffset: '0 0', + normalScale: '0.5 0.5', + opacity: 0.8 + }, + 'wobble-normal': {}, + 'wobble-geometry-box': {} + } +}); diff --git a/src/tested/aframe-streetmix-parsers-tested.js b/src/tested/aframe-streetmix-parsers-tested.js index beb44a499..aff283b67 100644 --- a/src/tested/aframe-streetmix-parsers-tested.js +++ b/src/tested/aframe-streetmix-parsers-tested.js @@ -165,34 +165,3 @@ function getAmbientSoundJSON (buildingsArray) { // eslint-disable-line no-unused return soundsArray; } module.exports.getAmbientSoundJSON = getAmbientSoundJSON; - -// possible input values: grass, fence, narrow, wide, waterfront, residential, parking-lot -function createGroundArray (buildingString, length) { // eslint-disable-line no-unused-vars - var repeatY = length / 30; - var repeatX = 1; - var groundArray = []; - var mixin = 'ground-grass'; // default output is grass ground type - - if (buildingString === 'waterfront') { return groundArray; } - if (['narrow', 'wide', 'arcade'].includes(buildingString)) { mixin = 'ground-asphalt'; } - if (buildingString === 'parking-lot') { - mixin = 'ground-parking-lot'; - repeatX = 0.5; - } - if (buildingString === 'arcade') { - mixin = 'ground-tiled-concrete'; - repeatY = length / 2; - repeatX = 20; - } - var groundEntity = { - tag: 'a-entity', - position: '0 -0.2 0', - mixin: mixin, - geometry: 'height: ' + length + ';', - material: 'repeat: ' + repeatX + ' ' + repeatY + ';' - }; - groundArray.push(groundEntity); - - return groundArray; -} -module.exports.createGroundArray = createGroundArray; diff --git a/test/aframe-streetmix-parsers-test.js b/test/aframe-streetmix-parsers-test.js index a598db812..7a65f02ef 100644 --- a/test/aframe-streetmix-parsers-test.js +++ b/test/aframe-streetmix-parsers-test.js @@ -53,33 +53,6 @@ describe('A-Frame Streetmix Parsers', function () { }); }); - describe('#createGroundArray()', function () { - it('createGroundArray("grass") should return array with one dictionary for a-entity with mixin ground-grass', function () { - assert.deepStrictEqual( - streetmixParsersTested.createGroundArray('grass', 150), - [{ tag: 'a-entity', mixin: 'ground-grass', position: '0 -0.2 0', geometry: 'height: 150;', material: 'repeat: 1 5;' }] - ); - }); - it('createGroundArray("parking-lot") should return array with one dictionary for a-entity with mixin ground-parking-lot', function () { - assert.deepStrictEqual( - streetmixParsersTested.createGroundArray('parking-lot', 150), - [{ mixin: 'ground-parking-lot', position: '0 -0.2 0', tag: 'a-entity', geometry: 'height: 150;', material: 'repeat: 0.5 5;' }] - ); - }); - it('createGroundArray("jiberish") should return array with one dictionary for a-entity with mixin ground-grass', function () { - assert.deepStrictEqual( - streetmixParsersTested.createGroundArray('jiberish', 75), - [{ mixin: 'ground-grass', position: '0 -0.2 0', tag: 'a-entity', geometry: 'height: 75;', material: 'repeat: 1 2.5;' }] - ); - }); - it('createGroundArray("narrow") should return array with one dictionary for a-entity with mixin ground-asphalt', function () { - assert.deepStrictEqual( - streetmixParsersTested.createGroundArray('narrow', 30), - [{ mixin: 'ground-asphalt', position: '0 -0.2 0', tag: 'a-entity', geometry: 'height: 30;', material: 'repeat: 1 1;' }] - ); - }); - }); - describe('#getAmbientSoundJSON()', function () { it('getAmbientSoundJSON(["narrow", "wide"]) should return array with one dictionary for a-entity with sound src #ambientmp3', function () { assert.deepStrictEqual(