Files
junisaber/src/components/beat.js
2018-10-10 02:53:00 -07:00

495 lines
18 KiB
JavaScript

var SIGN_MATERIAL = {shader: 'flat', color: '#88f'};
var auxObj3D = new THREE.Object3D();
/**
* Create beat from pool, collision detection, clipping planes.
*/
AFRAME.registerComponent('beat', {
schema: {
color: {default: 'red', oneOf: ['red', 'blue']},
debug: {default: false},
size: {default: 0.30},
speed: {default: 1.0},
type: {default: 'arrow', oneOf: ['arrow', 'dot', 'mine']}
},
materialColor: {
blue: '#08083E',
red: '#290404'
},
cutColor: {
blue: '#b3dcff',
red: '#ffb3ca'
},
models: {
arrow: 'beatObjTemplate',
dot: 'beatObjTemplate',
mine: 'mineObjTemplate'
},
signModels: {
arrow: 'arrowObjTemplate',
dot: 'dotObjTemplate'
},
init: function () {
var el = this.el;
var color;
var size = this.data.size;
this.beatBoundingBox = new THREE.Box3();
this.boundingBox = new THREE.Box3();
this.saberEls = this.el.sceneEl.querySelectorAll('[saber-controls]');
this.backToPool = false;
this.gravityVelocity = 0;
this.returnToPoolTimer = 800;
this.rightCutPlanePoints = [];
this.leftCutPlanePoints = [];
this.wrongElLeft = document.getElementById('wrongLeft');
this.wrongElRight = document.getElementById('wrongRight');
this.missElLeft = document.getElementById('missLeft');
this.missElRight = document.getElementById('missRight');
this.particles = document.getElementById('saberParticles');
this.initBlock();
this.initColliders();
this.initFragments();
},
update: function () {
this.updateBlock();
this.updateFragments();
if (this.data.type === 'mine') {
this.poolName = `pool__beat-mine`;
} else {
this.poolName = `pool__beat-${this.data.type}-${this.data.color}`;
}
},
initBlock: function () {
var el = this.el;
var blockEl = this.blockEl = document.createElement('a-entity');
var signEl = this.signEl = document.createElement('a-entity');
blockEl.setAttribute('mixin', 'beatBlock');
blockEl.setAttribute('mixin', 'beatSign');
// Small offset to prevent z-fighting when the blocks are far away
signEl.object3D.position.z += 0.02;
blockEl.appendChild(signEl);
el.appendChild(blockEl);
},
updateBlock: function () {
var blockEl = this.blockEl;
var signEl = this.signEl;
blockEl.setAttribute('material', {
metalness: 0.6,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color]
});
this.setObjModelFromTemplate(blockEl, this.models[this.data.type]);
// Model is 0.29 size. We make it 1.0 so we can easily scale based on 1m size.
blockEl.object3D.scale.multiplyScalar(3.45).multiplyScalar(this.data.size);
if (this.data.type === 'mine') {
let model = evt.detail.model.children[0];
model.material = this.el.sceneEl.components.stagecolors.mineMaterial;
}
signEl.setAttribute('material', SIGN_MATERIAL);
this.setObjModelFromTemplate(signEl, this.signModels[this.data.type]);
},
initColliders: function () {
var data = this.data;
var i;
var size;
var hitColliderConfiguration = {
position: {x: 0, y: data.size / 2, z: 0},
size: {width: data.size, height: data.size / 5.0, depth: data.size}
};
var hitColliderEl = this.hitColliderEl = document.createElement('a-entity');
hitColliderEl.setAttribute('geometry', {
primitive: 'box',
height: hitColliderConfiguration.size.height,
width: hitColliderConfiguration.size.width,
depth: hitColliderConfiguration.size.depth
});
hitColliderEl.object3D.position.copy(hitColliderConfiguration.position);
hitColliderEl.object3D.visible = false;
this.el.appendChild(hitColliderEl);
if (data.debug) {
hitColliderEl.object3D.visible = true;
hitColliderEl.setAttribute('material', 'color', 'purple');
}
},
initFragments: function () {
var partEl;
var cutEl;
var color = this.data.color === 'red' ? '#5b0502' : '#083771';
var size = this.data.size;
var geometry = {primitive: 'box', height: size, width: size, depth: size};
this.cutDirection = new THREE.Vector3();
this.rotationAxis = new THREE.Vector3();
partEl = this.partLeftEl = document.createElement('a-entity');
cutEl = this.cutLeftEl = document.createElement('a-entity');
partEl.appendChild(cutEl);
this.el.appendChild(partEl);
partEl = this.partRightEl = document.createElement('a-entity');
cutEl = this.cutRightEl = document.createElement('a-entity');
partEl.appendChild(cutEl);
this.el.appendChild(partEl);
this.initCuttingClippingPlanes();
},
updateFragments: function () {
var cutLeftEl = this.cutLeftEl;
var cutRightEl = this.cutRightEl;
var partLeftEl = this.partLeftEl;
var partRightEl = this.partRightEl;
partLeftEl.setAttribute('material', {
metalness: 0.8,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color],
side: 'double'
});
this.setObjModelFromTemplate(partLeftEl, this.models.dot);
partLeftEl.object3D.visible = false;
cutLeftEl.setAttribute('material', {
shader: 'flat',
color: this.data.cutColor,
side: 'double'
});
this.setObjModelFromTemplate(cutLeftEl, this.models.dot);
partRightEl.setAttribute('material', {
metalness: 0.8,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color],
side: 'double'
});
this.setObjModelFromTemplate(partRightEl, this.models.dot);
partRightEl.object3D.visible = false;
cutRightEl.setAttribute('material', {
shader: 'flat',
color: this.data.cutColor,
side: 'double'
});
this.setObjModelFromTemplate(cutRightEl, this.models.dot);
},
wrongCut: function (hand) {
var wrongEl = hand === 'left' ? this.wrongElLeft : this.wrongElRight;
if (!wrongEl) { return; }
wrongEl.object3D.position.copy(this.el.object3D.position);
wrongEl.object3D.position.y += 0.2;
wrongEl.object3D.position.z -= 0.5;
wrongEl.object3D.visible = true;
wrongEl.emit('beatwrong', null, false);
this.destroyed = true;
},
destroyBeat: (function () {
var parallelPlaneMaterial = new THREE.MeshBasicMaterial({color: '#00008b', side: THREE.DoubleSide});
var planeMaterial = new THREE.MeshBasicMaterial({color: 'grey', side: THREE.DoubleSide});
var point1 = new THREE.Vector3();
var point2 = new THREE.Vector3();
var point3 = new THREE.Vector3();
return function (saberEl) {
var coplanarPoint;
var cutThickness = this.cutThickness = 0.02;
var direction = this.cutDirection;
var focalPoint;
var i;
var leftBorderInnerPlane = this.leftBorderInnerPlane;
var leftBorderOuterPlane = this.leftBorderOuterPlane;
var leftCutPlane = this.leftCutPlane;
var parallelPlane2;
var parallelPlane;
var planeGeometry;
var planeMesh;
var rightBorderInnerPlane = this.rightBorderInnerPlane;
var rightBorderOuterPlane = this.rightBorderOuterPlane;
var rightCutPlane = this.rightCutPlane;
var trailPoints = saberEl.components.trail.saberTrajectory;
point1.copy(trailPoints[0].top);
point2.copy(trailPoints[0].center);
point3.copy(trailPoints[trailPoints.length - 1].top);
direction.copy(point1).sub(point3);
this.partLeftEl.object3D.position.set(0, 0, 0);
this.partLeftEl.object3D.rotation.set(0, 0, 0)
this.partLeftEl.object3D.updateMatrixWorld();
this.partRightEl.object3D.position.set(0, 0, 0);
this.partRightEl.object3D.rotation.set(0, 0, 0);
this.partRightEl.object3D.updateMatrixWorld();
this.rightCutPlanePoints.length = 0;
this.rightCutPlanePoints.push(this.partRightEl.object3D.worldToLocal(point1.clone()));
this.rightCutPlanePoints.push(this.partRightEl.object3D.worldToLocal(point2.clone()));
this.rightCutPlanePoints.push(this.partRightEl.object3D.worldToLocal(point3.clone()));
this.leftCutPlanePoints.length = 0;
this.leftCutPlanePoints.push(this.partLeftEl.object3D.worldToLocal(point3.clone()));
this.leftCutPlanePoints.push(this.partLeftEl.object3D.worldToLocal(point2.clone()));
this.leftCutPlanePoints.push(this.partLeftEl.object3D.worldToLocal(point1.clone()));
this.generateCutClippingPlanes();
if (this.data.debug) {
coplanarPoint = new THREE.Vector3();
planeGeometry = new THREE.PlaneGeometry(4.0, 4.0, 1.0, 1.0);
rightCutPlane.coplanarPoint(coplanarPoint);
planeGeometry.lookAt(rightCutPlane.normal);
planeGeometry.translate(coplanarPoint.x, coplanarPoint.y, coplanarPoint.z);
planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
this.el.sceneEl.setObject3D('rightCutPlane', planeMesh);
planeGeometry = new THREE.PlaneGeometry(4.0, 4.0, 1.0, 1.0);
rightBorderOuterPlane.coplanarPoint(coplanarPoint);
planeGeometry.lookAt(rightBorderOuterPlane.normal);
planeGeometry.translate(coplanarPoint.x, coplanarPoint.y, coplanarPoint.z);
parallelPlaneMesh = new THREE.Mesh(planeGeometry, parallelPlaneMaterial);
this.el.sceneEl.setObject3D('planeParallel', parallelPlaneMesh);
}
this.blockEl.object3D.visible = false;
const partRightMaterial = this.partRightEl.getObject3D('mesh').material;
partRightMaterial.clippingPlanes = partRightMaterial.clippingPlanes || [];
partRightMaterial.clippingPlanes.length = 0;
partRightMaterial.clippingPlanes.push(rightCutPlane);
const cutRightMaterial = this.cutRightEl.getObject3D('mesh').material;
cutRightMaterial.clippingPlanes = cutRightMaterial.clippingPlanes || [];
cutRightMaterial.clippingPlanes.length = 0
cutRightMaterial.clippingPlanes.push(rightBorderOuterPlane)
cutRightMaterial.clippingPlanes.push(rightBorderInnerPlane)
const partLeftMaterial = this.partLeftEl.getObject3D('mesh').material;
partLeftMaterial.clippingPlanes = partLeftMaterial.clippingPlanes || [];
partLeftMaterial.clippingPlanes.length = 0;
partLeftMaterial.clippingPlanes.push(leftCutPlane);
const cutLeftMaterial = this.cutLeftEl.getObject3D('mesh').material;
cutLeftMaterial.clippingPlanes = cutLeftMaterial.clippingPlanes || [];
cutLeftMaterial.clippingPlanes.length = 0;
cutLeftMaterial.clippingPlanes.push(leftBorderInnerPlane)
cutLeftMaterial.clippingPlanes.push(leftBorderOuterPlane)
this.partLeftEl.object3D.visible = true;
this.partRightEl.object3D.visible = true;
this.el.sceneEl.renderer.localClippingEnabled = true;
this.destroyed = true;
this.gravityVelocity = 0.1;
this.rotationAxis.copy(this.rightCutPlanePoints[0]).sub(this.rightCutPlanePoints[1]);
this.returnToPoolTimer = 800;
auxObj3D.up.copy(rightCutPlane.normal);
auxObj3D.lookAt(direction);
this.particles.emit('explode', {
position: this.el.object3D.position,
rotation: auxObj3D.rotation
});
}
})(),
pause: function () {
this.el.object3D.visible = false;
this.partLeftEl.object3D.visible = false;
this.partRightEl.object3D.visible = false;
},
play: function () {
this.destroyed = false;
this.el.object3D.visible = true;
this.blockEl.object3D.visible = true;
},
initCuttingClippingPlanes: function () {
this.leftCutPlanePointsWorld = [
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3()
];
this.rightCutPlanePointsWorld = [
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3()
];
this.rightCutPlane = new THREE.Plane();
this.rightBorderOuterPlane = new THREE.Plane();
this.rightBorderInnerPlane = new THREE.Plane();
this.leftCutPlane = new THREE.Plane();
this.leftBorderOuterPlane = new THREE.Plane();
this.leftBorderInnerPlane = new THREE.Plane();
},
generateCutClippingPlanes: function () {
var leftBorderInnerPlane = this.leftBorderInnerPlane;
var leftBorderOuterPlane = this.leftBorderOuterPlane;
var leftCutPlane = this.leftCutPlane;
var leftCutPlanePointsWorld = this.leftCutPlanePointsWorld;
var partLeftEl = this.partLeftEl;
var partRightEl = this.partRightEl;
var rightBorderInnerPlane = this.rightBorderInnerPlane;
var rightBorderOuterPlane = this.rightBorderOuterPlane;
var rightCutPlane = this.rightCutPlane;
var rightCutPlanePointsWorld = this.rightCutPlanePointsWorld;
partRightEl.object3D.updateMatrixWorld();
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[0].copy(this.rightCutPlanePoints[0]));
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[1].copy(this.rightCutPlanePoints[1]));
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[2].copy(this.rightCutPlanePoints[2]));
partLeftEl.object3D.updateMatrixWorld();
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[0].copy(this.leftCutPlanePoints[0]));
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[1].copy(this.leftCutPlanePoints[1]));
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[2].copy(this.leftCutPlanePoints[2]));
rightCutPlane.setFromCoplanarPoints(rightCutPlanePointsWorld[0], rightCutPlanePointsWorld[1], rightCutPlanePointsWorld[2]);
rightBorderOuterPlane.set(rightCutPlane.normal, rightCutPlane.constant + this.cutThickness);
leftCutPlane.setFromCoplanarPoints(leftCutPlanePointsWorld[0], leftCutPlanePointsWorld[1], leftCutPlanePointsWorld[2]);
leftBorderOuterPlane.set(leftCutPlane.normal, leftCutPlane.constant + this.cutThickness);
rightBorderInnerPlane.setFromCoplanarPoints(rightCutPlanePointsWorld[2], rightCutPlanePointsWorld[1], rightCutPlanePointsWorld[0]);
leftBorderInnerPlane.setFromCoplanarPoints(leftCutPlanePointsWorld[2], leftCutPlanePointsWorld[1], leftCutPlanePointsWorld[0]);
},
returnToPool: function (force) {
if (!this.backToPool && !force) { return; }
this.el.sceneEl.components[this.poolName].returnEntity(this.el);
},
tock: (function () {
var leftCutNormal = new THREE.Vector3();
var leftRotation = 0;
var rightCutNormal = new THREE.Vector3();
var rightRotation = 0;
var rotationStep = 2 * Math.PI / 150;
return function (time, timeDelta) {
var beatBoundingBox;
var boundingBox;
var i;
var plane;
var saberBoundingBox;
var saberEls = this.saberEls;
if (!this.destroyed) {
if (!this.hitColliderEl.getObject3D('mesh')) { return; }
boundingBox = this.boundingBox.setFromObject(this.hitColliderEl.getObject3D('mesh'));
beatBoundingBox = this.beatBoundingBox.setFromObject(this.blockEl.getObject3D('mesh'));
for (i = 0; i < saberEls.length; i++) {
saberBoundingBox = saberEls[i].components['saber-controls'].boundingBox;
if (!boundingBox || !saberBoundingBox) { break; }
if (saberBoundingBox.intersectsBox(boundingBox)) {
this.destroyBeat(saberEls[i]);
this.el.sceneEl.emit('beathit', null, false);
break;
}
if (saberBoundingBox.intersectsBox(beatBoundingBox)) {
this.destroyBeat(saberEls[i]);
this.wrongCut(saberEls[i].getAttribute('saber-controls').hand);
this.el.sceneEl.emit('beatwrong', null, false);
break;
}
}
this.el.object3D.position.z += this.data.speed * (timeDelta / 1000);
this.backToPool = this.el.object3D.position.z >= 2;
} else {
// Update gravity velocity.
this.gravityVelocity = getGravityVelocity(this.gravityVelocity, timeDelta);
this.el.object3D.position.y += this.gravityVelocity * (timeDelta / 1000);
rightCutNormal.copy(this.rightCutPlane.normal).multiplyScalar((this.data.speed / 2) * (timeDelta / 500));
rightCutNormal.y = 0; // Y handled by gravity.
this.partRightEl.object3D.position.add(rightCutNormal);
this.partRightEl.object3D.setRotationFromAxisAngle(this.rotationAxis, rightRotation);
rightRotation = rightRotation >= 2 * Math.PI ? 0 : rightRotation + rotationStep;
leftCutNormal.copy(this.leftCutPlane.normal).multiplyScalar((this.data.speed / 2) * (timeDelta / 500));
leftCutNormal.y = 0; // Y handled by gravity.
this.partLeftEl.object3D.position.add(leftCutNormal);
this.partLeftEl.object3D.setRotationFromAxisAngle(this.rotationAxis, leftRotation);
leftRotation = leftRotation >= 2 * Math.PI ? 0 : leftRotation + rotationStep;
this.generateCutClippingPlanes();
this.returnToPoolTimer -= timeDelta;
this.backToPool = this.returnToPoolTimer <= 0;
}
this.returnToPool();
};
})(),
setObjModelFromTemplate: (function () {
const geometries = {};
return function (el, templateId) {
if (!geometries[templateId]) {
const templateEl = document.getElementById(templateId);
if (templateEl.getObject3D('mesh')) {
geometries[templateId] = templateEl.getObject3D('mesh').children[0].geometry;
} else {
templateEl.addEventListener('model-loaded', () => {
geometries[templateId] = templateEl.getObject3D('mesh').children[0].geometry;
this.setObjModelFromTemplate(el, templateId);
});
return;
}
}
if (!el.getObject3D('mesh')) { el.setObject3D('mesh', new THREE.Mesh()); }
el.getObject3D('mesh').geometry = geometries[templateId];
};
})()
});
/**
* Get velocity given current velocity using gravity acceleration.
*/
function getGravityVelocity (velocity, timeDelta) {
const GRAVITY = -9.8;
return velocity + (GRAVITY * (timeDelta / 1000));
}