import Touch from './helpers/Touch'
import { TweenLite } from 'gsap'
import {getUVFromViewPortCoord} from "./helpers/MouseUVUtil"
const THREE = require('three')
import {
BaseBlend ,
BaseBlendConst
} from "./BaseBlend"
/**
* @typeDef {object} Blend.EVENTS__TYPE
* @property {string} BLEND_UNIT - emitted on blend value change. event value:number (0 to 1)
* @property {string} BLEND_TOUCH - emitted on touch or mousedown. event value: none
* @property {string} BLEND_TRANSITION_COMPLETE - emitted on blend value =1. can use to switch video cell targets. event value: none
*/
export const EVENTS = {
BLEND_UNIT: BaseBlendConst.EVENTS.BLEND_UNIT,
BLEND_TOUCH: 'BLEND_TOUCH',
BLEND_TRANSITION_COMPLETE:'BLEND_TRANSITION_COMPLETE',
}
/**@typeDef {object} Blend.FIT__TYPE
* @property {string} COVER - fill frame, maintain aspect ratio
* @property {string} CONTAIN - fit within frame, maintain aspect ratio
* @property {string} STRETCH - stretch to frame, dont maintain aspect ratio
*/
export const FIT = {
COVER: 'cover',
CONTAIN: 'contain',
STRETCH: 'stretch',
}
/**@typeDef {object} Blend.TOUCH_MODE__TYPE
* @property {string} NONE - for external-only control of touch/blend events
* @property {string} HOLD - transition to index2 on hold - transition to index1 on release
* @property {string} HOLD_THRESHOLD - trigger transition to index2 on hold duration, then index1 = index2. if threshold crossed - no release trigger
* @property {string} TOGGLE - trigger transition to index2 on press, then index1 = index2. no release trigger
*/
export const TOUCH_MODE = {
NONE:"none",
HOLD:"hold",
HOLD_THRESHOLD:"hold_threshold",
TOGGLE:"toggle"
}
export const createDefaultConfig = function(){
return {
media: null,
fit: FIT.CONTAIN, // contain | cover | stretch
cellGrid: {
x: 1,
y: 2,
},
cellIndexLength: null, //set if spritesheet max-cell-index is less than the full cell grid
cellUVTrim:0.985, // trim video cell texture lookups to avoid cell-bleed
vertexShader:null, //eg ./shaders/base.vert
fragmentShader:null, //eg ./shaders/circleFade.frag
//internalRenderer: if false : wont create renderer, scene, camera.
internalRenderer:true,
touchMode:TOUCH_MODE.HOLD,
touchEasing:0.5, //1 = no easing, 0.1 = take 30ish frames to move to new value
holdThreshold:0.25,//TOUCH_MODE.HOLD_THRESHOLD only - blend unit minimum to switch to next cellIndex
transitionPress:{
delay:0.0,
duration:1.0,
ease:"Sine.easeIn"
},
transitionRelease:{
delay:0.0,
duration:0.8,
ease:"Sine.easeOut"
},
blendEasing:0.3, //1 = no easing, 0.1 = take 30ish frames to move to new value
blendBiasFunc: (n)=> n, //null,//(n)=>{return n}, //bias the blend shader input (curve)
bleedBiasFunc: (n)=> window.Power1.easeOut.getRatio(n), //bias the radialBleed shader input (curve)
autoMaxRadius:true, // true: will automatically calculate the required maxRadius to cover the video for a radial based effect
mouseRayCasting: false, // if custom-transforming the video mesh, or adding mesh to another scene, set true to position mouse correctly
}
}
//better auto-complete
export const BlendConst = {
EVENTS,
FIT,
TOUCH_MODE,
createDefaultConfig
}
/**
* @extends BaseBlend
* @class
*/
export class Blend extends BaseBlend {
/** @property {...Blend.EVENTS__TYPE}**/
get EVENTS() { return EVENTS }
/** @property {...Blend.TOUCH_MODE__TYPE}**/
get TOUCH_MODE() { return TOUCH_MODE }
/** @property {...Blend.FIT__TYPE}**/
get FIT() { return FIT }
/** @property {...Blend.EVENTS__TYPE}**/
static get EVENTS() { return EVENTS }
/** @property {...Blend.TOUCH_MODE__TYPE}**/
static get TOUCH_MODE() { return TOUCH_MODE }
/** @property {...Blend.FIT__TYPE}**/
static get FIT() { return FIT }
/**
*
* @param {Object} config - configuration , see createDefaultConfig() for default values
* @param {(HTMLVideoElement|Media|MediaElementTrack)} config.media - must pass one of: (src/services/MediaService/Media, src/services/MediaService/tracks/MediaElementTrack, HTMLVideoElement)
*
* @param {string} [config.fit=contain] - video maintain-aspect-crop behaviour [contain | cover | stretch], see const FIT
*
* @param {object} [config.cellGrid={x:1, y:2}] - the x:column, y:row totals of the video spritesheet
* @param {number} config.cellGrid.x - the column total of the video spritesheet (x axis)
* @param {number} config.cellGrid.y - the row total of the video spritesheet (y axis)
*
* @param {integer} [config.cellIndexLength] - set if spritesheet max-cell-index is less than the full celltotal(w*h)
* @param {number} [config.cellUVTrim=0.985] - trim video cell texture lookups to avoid cell-bleed (notrim=1, trim to 25% borderwidth = 0.5
*
* @param {string} [config.vertexShader=./shaders/base.vert] - eg ./shaders/base.vert
* @param {string} [config.fragmentShader=./shaders/circleFade.frag] - eg ./shaders/circleFade.frag
* @param {boolean} [config.internalRenderer=true] - if false, wont create renderer, scene, camera.
* Intended to then use mesh/material in an externally defined scene.
* Side effects: fit should probably then be full uv coverage (fit:FIT.STRETCH)
* @param {string} [config.touchMode=hold] - (none|hold|hold_threshold|toggle) see const TOUCH_MODE
* @param {number} [config.touchEasing=0.5]
* @param {number} [config.holdThreshold=0.25]
*
* @param {object} [config.transitionPress={delay:0.0,duration:1.0,ease:"Sine.easeIn"}] - TweenMax props for transition of blend on press
* @param {number} config.transitionPress.delay - Seconds
* @param {number} config.transitionPress.duration - Seconds
* @param {String} config.transitionPress.ease
*
* @param {object} [config.transitionRelease={delay:0.0,duration:0.8,ease:"Sine.easeOut"}] - TweenMax props for transition of blend on release
* @param {number} config.transitionRelease.delay - Seconds
* @param {number} config.transitionRelease.duration - Seconds
* @param {String} config.transitionRelease.ease
*
*
* @param {number} [config.blendEasing=0.3]
* @param {function} [config.blendBiasFunc=(n)=> n]
* @param {function} [config.bleedBiasFunc=(n)=> window.Power1.easeOut.getRatio(n) ]
* @param {boolean} [config.autoMaxRadius=true]
* @param {boolean} [config.mouseRayCasting=false]
*/
constructor(config) {
config = Object.assign({},createDefaultConfig(),config)
super(config)
}
init(){
this.blendTarget = 0
this._blendEased = 0
super.init()
}
/** raf, extended from BaseBlend*/
update(){
this.updateTouch()
this.updateBlend(this.blendTarget)
super.update()
}
/** called per frame, updates any touch behaviour
* @param {number} target - blend value to bias/ease to
*/
updateBlend(target){
let c = this.config;
let blend = target;
this._blendEased += (blend - this._blendEased) * c.blendEasing
blend = this._blendEased;
this.blend = (c.blendBiasFunc) ? c.blendBiasFunc(blend) : blend
this.radiusBleed = (c.bleedBiasFunc) ? c.bleedBiasFunc(blend) : blend
}
//----TOUCH HANDLING - ( separate touch handler class?? ) ------------------------------------------------//
/** called per frame, updates any touch behaviour */
updateTouch(){
if (this.config.touchMode === TOUCH_MODE.NONE){return}
let touch = this._getTouch()
let released = (this._prevTouched && !touch.touching)
let pressed = (!this._prevTouched && touch.touching)
if (pressed){this.emit(EVENTS.BLEND_TOUCH)}
this._prevTouched = touch.touching;
this.touchX += (touch.x - this.touchX) * this.config.touchEasing
this.touchY += (touch.y - this.touchY) * this.config.touchEasing
if (this.config.touchMode === TOUCH_MODE.HOLD){ this._updateTouch_HOLD(pressed ,released) }
if (this.config.touchMode === TOUCH_MODE.TOGGLE){this._updateTouch_TOGGLE( pressed ,released) }
if (this.config.touchMode === TOUCH_MODE.HOLD_THRESHOLD){ this._updateTouch_HOLD_THRESHOLD(pressed ,released) }
}
setTouchImmediate(){
let touch = this._getTouch()
this.touchX = touch.x
this.touchY = touch.y
}
/* --------------END TOUCH HANDLING------------------------------------------------------------*/
/* --------------TRANSITION UTILS--------------------------------------------------------------*/
/**
* @method
* @param {function} [onComplete]
* @param {object} [tweenProps] - TweenMax props for transition of blend
* @param {number} tweenProps.delay - Seconds
* @param {number} tweenProps.duration - Seconds
* @param {String} tweenProps.ease
*/
toggleBlend = (onComplete, tweenProps ) => {
//tweenProps = {duration delay ease}
this._blendTweenTarget = (this._blendTweenTarget === 1) ? 0 : 1
this.tweenBlend(this._blendTweenTarget, onComplete, tweenProps );
}
/**
* @method
* @param {integer} fromIndex
* @param {integer} toIndex
* @param {integer} nextIndex2
* @param {function} [onComplete]
* @param {object} [tweenProps] - TweenMax props for transition of blend
* if not provided - uses config tweenProps
* @param {number} tweenProps.delay - Seconds
* @param {number} tweenProps.duration - Seconds
* @param {String} tweenProps.ease
*/
transitionToIndex = (fromIndex, toIndex, nextIndex2, onComplete, tweenProps ) => {
//tweenProps = {duration delay ease}
this.cellIndex1 = fromIndex
this.cellIndex2 = toIndex
this.tweenBlend(1, ()=>{
this.blend = this.blendTarget = this._blendEased = 0
this.cellIndex1 = toIndex
this.cellIndex2 = nextIndex2
if (onComplete){onComplete()}
}, tweenProps )
}
/**
* tweens to a given blend value
* @method
* @param {number blend = (0 to 1)
* @param {function } [onComplete]
* @param {object} [tweenProps] - TweenMax props for transition of blend
* if not provided - uses config tweenProps
* @param {number} tweenProps.delay - Seconds
* @param {number} tweenProps.duration - Seconds
* @param {String} tweenProps.ease
*/
tweenBlend =(blend, onComplete, tweenProps ) => {
let c = (blend === 0) ? this.config.transitionRelease : this.config.transitionPress
tweenProps = tweenProps || c;
this._blendTweenTarget = blend
let props = {
blendTarget:blend,
delay:tweenProps.delay,
ease:tweenProps.ease,
onComplete:() =>{
if (onComplete){onComplete()}
this._tweeningBlend = false
if(blend === 1){this.emit(EVENTS.BLEND_TRANSITION_COMPLETE)}
}
}
this._tweeningBlend = true;
TweenLite.killTweensOf(this, {blendTarget:true})
TweenLite.to(this, tweenProps.duration, props)
}
/* --------------END TRANSITION UTILS------------------------------------------------------------*/
destroy(){
this.touchUtil.destroy()
this.touchUtil = null
TweenLite.killTweensOf(this)
super.destroy()
}
/*--------------INTERNAL------------------------------------------------------------*/
_addEvents() {
this.touchUtil = new Touch(this.el)
super._addEvents()
}
_removeEvents() {
super._removeEvents()
}
_getTouch(){
this.__touch = this.__touch || {x:-1, y:-1, touching:false}
let rayCasting = this.config.mouseRayCasting
if (rayCasting){
let uv = getUVFromViewPortCoord(this.touchUtil.x,this.touchUtil.y, this.camera, [this.mesh])
if (uv){
this.__touch.x = uv.x
this.__touch.y = 1.0-uv.y
}
this.__touch.touching = (this.touchUtil.touching && uv)
}else{
this.__touch.x = this.touchUtil.x
this.__touch.y = this.touchUtil.y
this.__touch.touching = this.touchUtil.touching
}
return this.__touch
}
_updateTouch_HOLD( pressed ,released){
if (pressed){
this.setTouchImmediate()
this.tweenBlend(1)
}
if (released){
this.tweenBlend(0)
}
}
_updateTouch_TOGGLE( pressed ,released){
if (pressed && !this._tweeningBlend){
this.setTouchImmediate()
let next = (this.cellIndex2 +1) % this.config.cellIndexLength;
this.transitionToIndex(this.cellIndex1, this.cellIndex2, next );
}
}
_updateTouch_HOLD_THRESHOLD(pressed ,released){
if(this._canReleaseBlend === undefined){
this._canReleaseBlend = false
this._canPressBlend = true
}
if (pressed && this._canPressBlend){
this.setTouchImmediate()
this._canPressBlend = false
this._canReleaseBlend = true
let nextTargetIndex2 = (this.cellIndex2 +1) % this.config.cellIndexLength;
this.transitionToIndex(this.cellIndex1, this.cellIndex2, nextTargetIndex2, ()=>{this._canPressBlend = true})
} else if (this._canReleaseBlend && (0.2 < this.blend) ){
this._canReleaseBlend = false
this._canPressBlend = false
} else if (this._canReleaseBlend && released){
this.tweenBlend(0)
this._canReleaseBlend = false
this._canPressBlend = true
}
}
_killTweens = () => {
TweenLite.killTweensOf(this, {blendTarget:true})
}
}