魔方js小游戏

代码语言:html

所属分类:游戏

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The Cube</title>
    <meta name="viewport" content="width=device-width,height=device-height,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
    <style>
        *,*:before,*:after{-webkit-user-select:none;-moz-user-select:none;user-select:none;box-sizing:border-box;cursor:inherit;margin:0;padding:0;outline:0;font-size:inherit;font-family:inherit;font-weight:inherit;font-style:inherit;text-transform:uppercase}*:focus{outline:0}html{-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;overflow:hidden;height:100%}body{font-family:bungeefont,sans-serif;font-weight:400;font-style:normal;line-height:1;cursor:default;overflow:hidden;height:100%;font-size:5rem}.icon{display:inline-block;font-size:inherit;overflow:visible;vertical-align:-.125em;preserveaspectratio:none}.range{position:relative;width:14em;z-index:1;opacity:0}.range:not(:last-child){margin-bottom:2em}.range__label{position:relative;font-size:.9em;line-height:.75em;padding-bottom:.5em;z-index:2}.range__track{position:relative;height:1em;margin-left:.5em;margin-right:.5em;z-index:3}.range__track-line{position:absolute;background:rgba(0,0,0,.2);height:2px;top:50%;margin-top:-1px;left:-.5em;right:-.5em;transform-origin:left center}.range__handle{position:absolute;width:0;height:0;top:50%;left:0;cursor:pointer;z-index:1}.range__handle div{transition:background 500ms ease;position:absolute;left:0;top:0;width:.9em;height:.9em;border-radius:.2em;margin-left:-.45em;margin-top:-.45em;background:#41aac8;border-bottom:2px solid rgba(0,0,0,.2)}.range.is-active .range__handle div{transform:scale(1.25)}.range__handle:after{content:"";position:absolute;left:0;top:0;width:3em;height:3em;margin-left:-1.5em;margin-top:-1.5em}.range__list{display:flex;flex-flow:row nowrap;justify-content:space-between;position:relative;padding-top:.5em;font-size:.55em;color:rgba(0,0,0,.5);z-index:1}.stats{position:relative;width:14em;z-index:1;display:flex;justify-content:space-between;opacity:0}.stats:not(:last-child){margin-bottom:1.5em}.stats i{display:inline-block;color:rgba(0,0,0,.5);font-size:.9em}.stats b{display:inline-block;font-size:.9em}.text{position:absolute;left:0;right:0;text-align:center;line-height:.75;perspective:100rem;opacity:0}.text i{display:inline-block;opacity:0;white-space:pre-wrap}.text--title{bottom:75%;font-size:4.4em;height:1.2em}.text--title span{display:block}.text--title span:first-child{font-size:.5em;margin-bottom:.2em}.text--note{top:87%;font-size:1em}.text--timer{bottom:78%;font-size:3.5em;line-height:1}.text--complete,.text--best-time{font-size:1.5em;top:83%;line-height:1em}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border-radius:0;border-width:0;position:absolute;pointer-events:none;font-size:1.2em;color:rgba(0,0,0,.25);opacity:0}.btn:after{position:absolute;content:"";width:3em;height:3em;left:50%;top:50%;margin-left:-1.5em;margin-top:-1.5em;border-radius:100%}.btn--bl{bottom:.8em;left:.8em}.btn--br{bottom:.8em;right:.8em}.btn--bc{bottom:.8em;left:calc(50% - 0.5em)}.btn--pwa{transition:color 500ms ease;color:inherit;height:1em}.btn--pwa svg{font-size:.6em;margin:.35em 0}.btn svg{display:block}.ui{pointer-events:none;color:#070d15}.ui,.ui__background,.ui__game,.ui__texts,.ui__prefs,.ui__stats,.ui__buttons{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden}.ui__background{z-index:1;transition:background 500ms ease;background:#d1d5db}.ui__background:after{position:absolute;top:0;left:0;width:100%;height:100%;content:"";background-image:linear-gradient(to bottom,white 50%,rgba(255,255,255,0) 100%)}.ui__game{pointer-events:all;z-index:2}.ui__game canvas{display:block;width:100%;height:100%}.ui__texts{z-index:3}.ui__prefs,.ui__stats{display:flex;flex-flow:column nowrap;justify-content:center;align-items:center;overflow:hidden;z-index:4}.ui__buttons{z-index:5}.ui__notification{transition:transform 500ms ease,opacity 500ms ease;font-family:sans-serif;position:absolute;left:50%;bottom:.6em;padding:.6em;margin-left:-9.4em;width:18.8em;z-index:6;background:rgba(17,17,17,.9);border-radius:.8em;display:flex;align-items:center;flex-flow:row nowrap;color:#fff;user-select:none;opacity:0;pointer-events:none;transform:translateY(100%)}.ui__notification.is-active{opacity:1;pointer-events:all;transform:none}.ui__notification *{text-transform:none}.ui__notification-icon{background-size:100% 100%;left:.6em;top:.6em;width:2.8em;height:2.8em;border-radius:.5em;background:#fff;margin-right:.6em;display:block}.ui__notification-text{font-size:.75em;line-height:1.4em}.ui__notification-text .icon{color:#4f82fd;font-size:1.1em}.ui__notification-text b{font-weight:700}.btn--stats{visibility:hidden}
    </style>
</head>
<body>
    <div class="ui">
        <div class="ui__background"></div>
        <div class="ui__game"></div>
        <div class="ui__texts">
            <h1 class="text text--title">
                <span>THE</span>
                <span>CUBE</span>
            </h1>
            <div class="text text--note">
                双击即可开始
            </div>
            <div class="text text--timer">
                0:00
            </div>
            <div class="text text--complete">
                <span>Complete!</span>
            </div>
            <div class="text text--best-time">
                <icon trophy></icon>
                <span>Best Time!</span>
            </div>
        </div>
        <div class="ui__prefs">
            <range name="flip" title="Flip Type" list="Swift&nbsp;,Smooth,Bounce"></range>
            <range name="scramble" title="Scramble Length" list="20,25,30"></range>
            <range name="fov" title="Camera Angle" list="Ortographic,Perspective"></range>
            <range name="theme" title="Color Scheme" list="Cube,Erno,Dust,Camo,Rain"></range>
        </div>
        <div class="ui__stats">
            <div class="stats" name="total-solves">
                <i>Total solves:</i><b>-</b>
            </div>
            <div class="stats" name="best-time">
                <i>Best time:</i><b>-</b>
            </div>
            <div class="stats" name="worst-time">
                <i>Worst time:</i><b>-</b>
            </div>
            <div class="stats" name="average-5">
                <i>Average of 5:</i><b>-</b>
            </div>
            <div class="stats" name="average-12">
                <i>Average of 12:</i><b>-</b>
            </div>
            <div class="stats" name="average-25">
                <i>Average of 25:</i><b>-</b>
            </div>
        </div>
        <div class="ui__buttons">
            <button class="btn btn--bl btn--stats">
                <icon trophy></icon>
            </button>
            <button class="btn btn--bl btn--prefs">
                <icon settings></icon>
            </button>
            <button class="btn btn--bl btn--back">
                <icon back></icon>
            </button>
            <a href="#" class="btn btn--br btn--pwa">
            </button>
        </div>
    </div>
    <script src='http://repo.bfw.wiki/bfwrepo/js/three.js'></script>
    <script >
        const animationEngine=(()=>{let uniqueID=0;class AnimationEngine{constructor(){this.ids=[];this.animations={};this.update=this.update.bind(this);this.raf=0;this.time=0;}
update(){const now=performance.now();const delta=now-this.time;this.time=now;let i=this.ids.length;this.raf=i?requestAnimationFrame(this.update):0;while(i--)
this.animations[this.ids[i]]&&this.animations[this.ids[i]].update(delta);}
add(animation){animation.id=uniqueID++;this.ids.push(animation.id);this.animations[animation.id]=animation;if(this.raf!==0)return;this.time=performance.now();this.raf=requestAnimationFrame(this.update);}
remove(animation){const index=this.ids.indexOf(animation.id);if(index<0)return;this.ids.splice(index,1);delete this.animations[animation.id];animation=null;}}
return new AnimationEngine();})();class Animation{constructor(start){if(start===true)this.start();}
start(){animationEngine.add(this);}
stop(){animationEngine.remove(this);}
update(delta){}}
class World extends Animation{constructor(game){super(true);this.game=game;this.container=this.game.dom.game;this.scene=new THREE.Scene();this.renderer=new THREE.WebGLRenderer({antialias:true,alpha:true});this.renderer.setPixelRatio(window.devicePixelRatio);this.container.appendChild(this.renderer.domElement);this.camera=new THREE.PerspectiveCamera(2,1,0.1,10000);this.stage={width:2,height:3};this.fov=10;this.createLights();this.onResize=[];this.resize();window.addEventListener('resize',()=>this.resize(),false);}
update(){this.renderer.render(this.scene,this.camera);}
resize(){this.width=this.container.offsetWidth;this.height=this.container.offsetHeight;this.renderer.setSize(this.width,this.height);this.camera.fov=this.fov;this.camera.aspect=this.width/this.height;const aspect=this.stage.width/this.stage.height;const fovRad=this.fov*THREE.Math.DEG2RAD;let distance=(aspect<this.camera.aspect)?(this.stage.height/2)/Math.tan(fovRad/2):(this.stage.width/this.camera.aspect)/(2*Math.tan(fovRad/2));distance*=0.5;this.camera.position.set(distance,distance,distance);this.camera.lookAt(this.scene.position);this.camera.updateProjectionMatrix();const docFontSize=(aspect<this.camera.aspect)?(this.height/100)*aspect:this.width/100;document.documentElement.style.fontSize=docFontSize+'px';if(this.onResize)this.onResize.forEach(cb=>cb());}
createLights(){this.lights={holder:new THREE.Object3D,ambient:new THREE.AmbientLight(0xffffff,0.69),front:new THREE.DirectionalLight(0xffffff,0.36),back:new THREE.DirectionalLight(0xffffff,0.19),};this.lights.front.position.set(1.5,5,3);this.lights.back.position.set(-1.5,-5,-3);this.lights.holder.add(this.lights.ambient);this.lights.holder.add(this.lights.front);this.lights.holder.add(this.lights.back);this.scene.add(this.lights.holder);}
enableShadows(){this.renderer.shadowMap.enabled=true;this.renderer.shadowMap.type=THREE.PCFSoftShadowMap;this.lights.front.castShadow=true;this.lights.front.shadow.mapSize.width=512;this.lights.front.shadow.mapSize.height=512;var d=1.5;this.lights.front.shadow.camera.left=-d;this.lights.front.shadow.camera.right=d;this.lights.front.shadow.camera.top=d;this.lights.front.shadow.camera.bottom=-d;this.lights.front.shadow.camera.near=1;this.lights.front.shadow.camera.far=9;this.game.cube.holder.traverse(node=>{if(node instanceof THREE.Mesh){node.castShadow=true;node.receiveShadow=true;}});}}
function RoundedBoxGeometry(size,radius,radiusSegments){THREE.BufferGeometry.call(this);this.type='RoundedBoxGeometry';radiusSegments=!isNaN(radiusSegments)?Math.max(1,Math.floor(radiusSegments)):1;var width,height,depth;width=height=depth=size;radius=size*radius;radius=Math.min(radius,Math.min(width,Math.min(height,Math.min(depth)))/2);var edgeHalfWidth=width/2-radius;var edgeHalfHeight=height/2-radius;var edgeHalfDepth=depth/2-radius;this.parameters={width:width,height:height,depth:depth,radius:radius,radiusSegments:radiusSegments};var rs1=radiusSegments+1;var totalVertexCount=(rs1*radiusSegments+1)<<3;var positions=new THREE.BufferAttribute(new Float32Array(totalVertexCount*3),3);var normals=new THREE.BufferAttribute(new Float32Array(totalVertexCount*3),3);var
cornerVerts=[],cornerNormals=[],normal=new THREE.Vector3(),vertex=new THREE.Vector3(),vertexPool=[],normalPool=[],indices=[];var
lastVertex=rs1*radiusSegments,cornerVertNumber=rs1*radiusSegments+1;doVertices();doFaces();doCorners();doHeightEdges();doWidthEdges();doDepthEdges();function doVertices(){var cornerLayout=[new THREE.Vector3(1,1,1),new THREE.Vector3(1,1,-1),new THREE.Vector3(-1,1,-1),new THREE.Vector3(-1,1,1),new THREE.Vector3(1,-1,1),new THREE.Vector3(1,-1,-1),new THREE.Vector3(-1,-1,-1),new THREE.Vector3(-1,-1,1)];for(var j=0;j<8;j++){cornerVerts.push([]);cornerNormals.push([]);}
var PIhalf=Math.PI/2;var cornerOffset=new THREE.Vector3(edgeHalfWidth,edgeHalfHeight,edgeHalfDepth);for(var y=0;y<=radiusSegments;y++){var v=y/radiusSegments;var va=v*PIhalf;var cosVa=Math.cos(va);var sinVa=Math.sin(va);if(y==radiusSegments){vertex.set(0,1,0);var vert=vertex.clone().multiplyScalar(radius).add(cornerOffset);cornerVerts[0].push(vert);vertexPool.push(vert);var norm=vertex.clone();cornerNormals[0].push(norm);normalPool.push(norm);continue;}
for(var x=0;x<=radiusSegments;x++){var u=x/radiusSegments;var ha=u*PIhalf;vertex.x=cosVa*Math.cos(ha);vertex.y=sinVa;vertex.z=cosVa*Math.sin(ha);var vert=vertex.clone().multiplyScalar(radius).add(cornerOffset);cornerVerts[0].push(vert);vertexPool.push(vert);var norm=vertex.clone().normalize();cornerNormals[0].push(norm);normalPool.push(norm);}}
for(var i=1;i<8;i++){for(var j=0;j<cornerVerts[0].length;j++){var vert=cornerVerts[0][j].clone().multiply(cornerLayout[i]);cornerVerts[i].push(vert);vertexPool.push(vert);var norm=cornerNormals[0][j].clone().multiply(cornerLayout[i]);cornerNormals[i].push(norm);normalPool.push(norm);}}}
function doCorners(){var flips=[true,false,true,false,false,true,false,true];var lastRowOffset=rs1*(radiusSegments-1);for(var i=0;i<8;i++){var cornerOffset=cornerVertNumber*i;for(var v=0;v<radiusSegments-1;v++){var r1=v*rs1;var r2=(v+1)*rs1;for(var u=0;u<radiusSegments;u++){var u1=u+1;var a=cornerOffset+r1+u;var b=cornerOffset+r1+u1;var c=cornerOffset+r2+u;var d=cornerOffset+r2+u1;if(!flips[i]){indices.push(a);indices.push(b);indices.push(c);indices.push(b);indices.push(d);indices.push(c);}else{indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);}}}
for(var u=0;u<radiusSegments;u++){var a=cornerOffset+lastRowOffset+u;var b=cornerOffset+lastRowOffset+u+1;var c=cornerOffset+lastVertex;if(!flips[i]){indices.push(a);indices.push(b);indices.push(c);}else{indices.push(a);indices.push(c);indices.push(b);}}}}
function doFaces(){var a=lastVertex;var b=lastVertex+cornerVertNumber;var c=lastVertex+cornerVertNumber*2;var d=lastVertex+cornerVertNumber*3;indices.push(a);indices.push(b);indices.push(c);indices.push(a);indices.push(c);indices.push(d);a=lastVertex+cornerVertNumber*4;b=lastVertex+cornerVertNumber*5;c=lastVertex+cornerVertNumber*6;d=lastVertex+cornerVertNumber*7;indices.push(a);indices.push(c);indices.push(b);indices.push(a);indices.push(d);indices.push(c);a=0;b=cornerVertNumber;c=cornerVertNumber*4;d=cornerVertNumber*5;indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);a=cornerVertNumber*2;b=cornerVertNumber*3;c=cornerVertNumber*6;d=cornerVertNumber*7;indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);a=radiusSegments;b=radiusSegments+cornerVertNumber*3;c=radiusSegments+cornerVertNumber*4;d=radiusSegments+cornerVertNumber*7;indices.push(a);indices.push(b);indices.push(c);indices.push(b);indices.push(d);indices.push(c);a=radiusSegments+cornerVertNumber;b=radiusSegments+cornerVertNumber*2;c=radiusSegments+cornerVertNumber*5;d=radiusSegments+cornerVertNumber*6;indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);}
function doHeightEdges(){for(var i=0;i<4;i++){var cOffset=i*cornerVertNumber;var cRowOffset=4*cornerVertNumber+cOffset;var needsFlip=i&1===1;for(var u=0;u<radiusSegments;u++){var u1=u+1;var a=cOffset+u;var b=cOffset+u1;var c=cRowOffset+u;var d=cRowOffset+u1;if(!needsFlip){indices.push(a);indices.push(b);indices.push(c);indices.push(b);indices.push(d);indices.push(c);}else{indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);}}}}
function doDepthEdges(){var cStarts=[0,2,4,6];var cEnds=[1,3,5,7];for(var i=0;i<4;i++){var cStart=cornerVertNumber*cStarts[i];var cEnd=cornerVertNumber*cEnds[i];var needsFlip=1>=i;for(var u=0;u<radiusSegments;u++){var urs1=u*rs1;var u1rs1=(u+1)*rs1;var a=cStart+urs1;var b=cStart+u1rs1;var c=cEnd+urs1;var d=cEnd+u1rs1;if(needsFlip){indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);}else{indices.push(a);indices.push(b);indices.push(c);indices.push(b);indices.push(d);indices.push(c);}}}}
function doWidthEdges(){var end=radiusSegments-1;var cStarts=[0,1,4,5];var cEnds=[3,2,7,6];var needsFlip=[0,1,1,0];for(var i=0;i<4;i++){var cStart=cStarts[i]*cornerVertNumber;var cEnd=cEnds[i]*cornerVertNumber;for(var u=0;u<=end;u++){var a=cStart+radiusSegments+u*rs1;var b=cStart+(u!=end?radiusSegments+(u+1)*rs1:cornerVertNumber-1);var c=cEnd+radiusSegments+u*rs1;var d=cEnd+(u!=end?radiusSegments+(u+1)*rs1:cornerVertNumber-1);if(!needsFlip[i]){indices.push(a);indices.push(b);indices.push(c);indices.push(b);indices.push(d);indices.push(c);}else{indices.push(a);indices.push(c);indices.push(b);indices.push(b);indices.push(c);indices.push(d);}}}}
var index=0;for(var i=0;i<vertexPool.length;i++){positions.setXYZ(index,vertexPool[i].x,vertexPool[i].y,vertexPool[i].z);normals.setXYZ(index,normalPool[i].x,normalPool[i].y,normalPool[i].z);index++;}
this.setIndex(new THREE.BufferAttribute(new Uint16Array(indices),1));this.addAttribute('position',positions);this.addAttribute('normal',normals);}
RoundedBoxGeometry.prototype=Object.create(THREE.BufferGeometry.prototype);RoundedBoxGeometry.constructor=RoundedBoxGeometry;function RoundedPlaneGeometry(size,radius,depth){var x,y,width,height;x=y=-size/2;width=height=size;radius=size*radius;const shape=new THREE.Shape();shape.moveTo(x,y+radius);shape.lineTo(x,y+height-radius);shape.quadraticCurveTo(x,y+height,x+radius,y+height);shape.lineTo(x+width-radius,y+height);shape.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);shape.lineTo(x+width,y+radius);shape.quadraticCurveTo(x+width,y,x+width-radius,y);shape.lineTo(x+radius,y);shape.quadraticCurveTo(x,y,x,y+radius);const geometry=new THREE.ExtrudeBufferGeometry(shape,{depth:depth,bevelEnabled:false,curveSegments:3});return geometry;}
class Cube{constructor(game){this.game=game;this.geometry={pieceSize:1/3,pieceCornerRadius:0.12,edgeCornerRoundness:0.15,edgeScale:0.82,edgeDepth:0.01,};this.holder=new THREE.Object3D();this.object=new THREE.Object3D();this.animator=new THREE.Object3D();this.holder.add(this.animator);this.animator.add(this.object);this.cubes=[];this.generatePositions();this.generateModel();this.pieces.forEach(piece=>{this.cubes.push(piece.userData.cube);this.object.add(piece);});this.holder.traverse(node=>{if(node.frustumCulled)node.frustumCulled=false;});this.game.world.scene.add(this.holder);}
reset(){this.game.controls.edges.rotation.set(0,0,0);this.holder.rotation.set(0,0,0);this.object.rotation.set(0,0,0);this.animator.rotation.set(0,0,0);this.pieces.forEach(piece=>{piece.position.copy(piece.userData.start.position);piece.rotation.copy(piece.userData.start.rotation);});}
generatePositions(){let x,y,z;this.positions=[];for(x=0;x<3;x++){for(y=0;y<3;y++){for(z=0;z<3;z++){let position=new THREE.Vector3(x-1,y-1,z-1);let edges=[];if(x==0)edges.push(0);if(x==2)edges.push(1);if(y==0)edges.push(2);if(y==2)edges.push(3);if(z==0)edges.push(4);if(z==2)edges.push(5);position.edges=edges;this.positions.push(position);}}}}
generateModel(){this.pieces=[];this.edges=[];const pieceSize=1/3;const mainMaterial=new THREE.MeshLambertMaterial();const pieceMesh=new THREE.Mesh(new RoundedBoxGeometry(pieceSize,this.geometry.pieceCornerRadius,3),mainMaterial.clone());const edgeGeometry=RoundedPlaneGeometry(pieceSize,this.geometry.edgeCornerRoundness,this.geometry.edgeDepth);this.positions.forEach((position,index)=>{const piece=new THREE.Object3D();const pieceCube=pieceMesh.clone();const pieceEdges=[];piece.position.copy(position.clone().divideScalar(3));piece.add(pieceCube);piece.name=index;piece.edgesName='';position.edges.forEach(position=>{const edge=new THREE.Mesh(edgeGeometry,mainMaterial.clone());const name=['L','R','D','U','B','F'][position];const distance=pieceSize/2;edge.position.set(distance*[-1,1,0,0,0,0][position],distance*[0,0,-1,1,0,0][position],distance*[0,0,0,0,-1,1][position]);edge.rotation.set(Math.PI/2*[0,0,1,-1,0,0][position],Math.PI/2*[-1,1,0,0,2,0][position],0);edge.scale.set(this.geometry.edgeScale,this.geometry.edgeScale,this.geometry.edgeScale);edge.name=name;piece.add(edge);pieceEdges.push(name);this.edges.push(edge);});piece.userData.edges=pieceEdges;piece.userData.cube=pieceCube;piece.userData.start={position:piece.position.clone(),rotation:piece.rotation.clone(),};this.pieces.push(piece);});}}
const Easing={Power:{In:power=>{power=Math.round(power||1);return t=>Math.pow(t,power);},Out:power=>{power=Math.round(power||1);return t=>1-Math.abs(Math.pow(t-1,power));},InOut:power=>{power=Math.round(power||1);return t=>(t<0.5)?Math.pow(t*2,power)/2:(1-Math.abs(Math.pow((t*2-1)-1,power)))/2+0.5;},},Sine:{In:()=>t=>1+Math.sin(Math.PI/2*t-Math.PI/2),Out:()=>t=>Math.sin(Math.PI/2*t),InOut:()=>t=>(1+Math.sin(Math.PI*t-Math.PI/2))/2,},Back:{Out:s=>{s=s||1.70158;return t=>{return(t-=1)*t*((s+1)*t+s)+1;};},In:s=>{s=s||1.70158;return t=>{return t*t*((s+1)*t-s);};}},Elastic:{Out:(amplitude,period)=>{let PI2=Math.PI*2;let p1=(amplitude>=1)?amplitude:1;let p2=(period||0.3)/(amplitude<1?amplitude:1);let p3=p2/PI2*(Math.asin(1/p1)||0);p2=PI2/p2;return t=>{return p1*Math.pow(2,-10*t)*Math.sin((t-p3)*p2)+1}},},};class Tween extends Animation{constructor(options){super(false);this.duration=options.duration||500;this.easing=options.easing||(t=>t);this.onUpdate=options.onUpdate||(()=>{});this.onComplete=options.onComplete||(()=>{});this.delay=options.delay||false;this.yoyo=options.yoyo?false:null;this.progress=0;this.value=0;this.delta=0;this.getFromTo(options);if(this.delay)setTimeout(()=>super.start(),this.delay);else super.start();this.onUpdate(this);}
update(delta){const old=this.value*1;const direction=(this.yoyo===true)?-1:1;this.progress+=(delta/this.duration)*direction;this.value=this.easing(this.progress);this.delta=this.value-old;if(this.values!==null)this.updateFromTo();if(this.yoyo!==null)this.updateYoyo();else if(this.progress<=1)this.onUpdate(this);else{this.progress=1;this.value=1;this.onUpdate(this);this.onComplete(this);super.stop();}}
updateYoyo(){if(this.progress>1||this.progress<0){this.value=this.progress=(this.progress>1)?1:0;this.yoyo=!this.yoyo;}
this.onUpdate(this);}
updateFromTo(){this.values.forEach(key=>{this.target[key]=this.from[key]+(this.to[key]-this.from[key])*this.value;});}
getFromTo(options){if(!options.target||!options.to){this.values=null;return;}
this.target=options.target||null;this.from=options.from||{};this.to=options.to||null;this.values=[];if(Object.keys(this.from).length<1)
Object.keys(this.to).forEach(key=>{this.from[key]=this.target[key];});Object.keys(this.to).forEach(key=>{this.values.push(key);});}}
window.addEventListener('touchmove',()=>{});document.addEventListener('touchmove',event=>{event.preventDefault();},{passive:false});class Draggable{constructor(element,options){this.position={current:new THREE.Vector2(),start:new THREE.Vector2(),delta:new THREE.Vector2(),old:new THREE.Vector2(),drag:new THREE.Vector2(),};this.options=Object.assign({calcDelta:false,},options||{});this.element=element;this.touch=null;this.drag={start:(event)=>{if(event.type=='mousedown'&&event.which!=1)return;if(event.type=='touchstart'&&event.touches.length>1)return;this.getPositionCurrent(event);if(this.options.calcDelta){this.position.start=this.position.current.clone();this.position.delta.set(0,0);this.position.drag.set(0,0);}
this.touch=(event.type=='touchstart');this.onDragStart(this.position);window.addEventListener((this.touch)?'touchmove':'mousemove',this.drag.move,false);window.addEventListener((this.touch)?'touchend':'mouseup',this.drag.end,false);},move:(event)=>{if(this.options.calcDelta){this.position.old=this.position.current.clone();}
this.getPositionCurrent(event);if(this.options.calcDelta){this.position.delta=this.position.current.clone().sub(this.position.old);this.position.drag=this.position.current.clone().sub(this.position.start);}
this.onDragMove(this.position);},end:(event)=>{this.getPositionCurrent(event);this.onDragEnd(this.position);window.removeEventListener((this.touch)?'touchmove':'mousemove',this.drag.move,false);window.removeEventListener((this.touch)?'touchend':'mouseup',this.drag.end,false);},};this.onDragStart=()=>{};this.onDragMove=()=>{};this.onDragEnd=()=>{};this.enable();return this;}
enable(){this.element.addEventListener('touchstart',this.drag.start,false);this.element.addEventListener('mousedown',this.drag.start,false);return this;}
disable(){this.element.removeEventListener('touchstart',this.drag.start,false);this.element.removeEventListener('mousedown',this.drag.start,false);return this;}
getPositionCurrent(event){const dragEvent=event.touches?(event.touches[0]||event.changedTouches[0]):event;this.position.current.set(dragEvent.pageX,dragEvent.pageY);}
convertPosition(position){position.x=(position.x/this.element.offsetWidth)*2-1;position.y=-((position.y/this.element.offsetHeight)*2-1);return position;}}
const STILL=0;const PREPARING=1;const ROTATING=2;const ANIMATING=3;class Controls{constructor(game){this.game=game;this.flipConfig=0;this.flipEasings=[Easing.Power.Out(3),Easing.Sine.Out(),Easing.Back.Out(2)];this.flipSpeeds=[125,200,350];this.raycaster=new THREE.Raycaster();const helperMaterial=new THREE.MeshBasicMaterial({depthWrite:false,transparent:true,opacity:0,color:0x0033ff});this.group=new THREE.Object3D();this.game.cube.object.add(this.group);this.helper=new THREE.Mesh(new THREE.PlaneBufferGeometry(20,20),helperMaterial.clone(),);this.helper.rotation.set(0,Math.PI/4,0);this.game.world.scene.add(this.helper);this.edges=new THREE.Mesh(new THREE.BoxBufferGeometry(0.95,0.95,0.95),helperMaterial.clone(),);this.game.world.scene.add(this.edges);this.onSolved=()=>{};this.onMove=()=>{};this.momentum=[];this.scramble=null;this.state=STILL;this.initDraggable();}
enable(){this.draggable.enable();}
disable(){this.draggable.disable();}
initDraggable(){this.draggable=new Draggable(this.game.dom.game);this.draggable.onDragStart=position=>{if(this.scramble!==null)return;if(this.state===PREPARING||this.state===ROTATING)return;this.gettingDrag=this.state===ANIMATING;const edgeIntersect=this.getIntersect(position.current,this.edges,false);if(edgeIntersect!==false){this.dragNormal=edgeIntersect.face.normal.round();this.flipType='layer';this.attach(this.helper,this.edges);this.helper.rotation.set(0,0,0);this.helper.position.set(0,0,0);this.helper.lookAt(this.dragNormal);this.helper.translateZ(0.5);this.helper.updateMatrixWorld();this.detach(this.helper,this.edges);}else{this.dragNormal=new THREE.Vector3(0,0,1);this.flipType='cube';this.helper.position.set(0,0,0);this.helper.rotation.set(0,Math.PI/4,0);this.helper.updateMatrixWorld();}
const planeIntersect=this.getIntersect(position.current,this.helper,false).point;if(planeIntersect===false)return;this.dragCurrent=this.helper.worldToLocal(planeIntersect);this.dragTotal=new THREE.Vector3();this.state=(this.state===STILL)?PREPARING:this.state;};this.draggable.onDragMove=position=>{if(this.scramble!==null)return;if(this.state===STILL||(this.state===ANIMATING&&this.gettingDrag===false))return;const planeIntersect=this.getIntersect(position.current,this.helper,false);if(planeIntersect===false)return;const point=this.helper.worldToLocal(planeIntersect.point.clone());this.dragDelta=point.clone().sub(this.dragCurrent).setZ(0);this.dragTotal.add(this.dragDelta);this.dragCurrent=point;this.addMomentumPoint(this.dragDelta);if(this.state===PREPARING&&this.dragTotal.length()>0.05){this.dragDirection=this.getMainAxis(this.dragTotal);if(this.flipType==='layer'){const direction=new THREE.Vector3();direction[this.dragDirection]=1;const worldDirection=this.helper.localToWorld(direction).sub(this.helper.position);const objectDirection=this.edges.worldToLocal(worldDirection).round();this.flipAxis=objectDirection.cross(this.dragNormal).negate();this.dragIntersect=this.getIntersect(position.current,this.game.cube.cubes,true);this.selectLayer(this.getLayer(false));}else{const axis=(this.dragDirection!='x')?((this.dragDirection=='y'&&position.current.x>this.game.world.width/2)?'z':'x'):'y';this.flipAxis=new THREE.Vector3();this.flipAxis[axis]=1*((axis=='x')?-1:1);}
this.flipAngle=0;this.state=ROTATING;}else if(this.state===ROTATING){const rotation=this.dragDelta[this.dragDirection];if(this.flipType==='layer'){this.group.rotateOnAxis(this.flipAxis,rotation);this.flipAngle+=rotation;}else{this.edges.rotateOnWorldAxis(this.flipAxis,rotation);this.game.cube.object.rotation.copy(this.edges.rotation);this.flipAngle+=rotation;}}};this.draggable.onDragEnd=position=>{if(this.scramble!==null)return;if(this.state!==ROTATING){this.gettingDrag=false;this.state=STILL;return;}
this.state=ANIMATING;const momentum=this.getMomentum()[this.dragDirection];const flip=(Math.abs(momentum)>0.05&&Math.abs(this.flipAngle)<Math.PI/2);const angle=flip?this.roundAngle(this.flipAngle+Math.sign(this.flipAngle)*(Math.PI/4)):this.roundAngle(this.flipAngle);const delta=angle-this.flipAngle;if(this.flipType==='layer'){this.rotateLayer(delta,false,layer=>{this.state=this.gettingDrag?PREPARING:STILL;this.gettingDrag=false;this.checkIsSolved();});}else{this.rotateCube(delta,()=>{this.state=this.gettingDrag?PREPARING:STILL;this.gettingDrag=false;});}};}
rotateLayer(rotation,scramble,callback){const config=scramble?0:this.flipConfig;const easing=this.flipEasings[config];const duration=this.flipSpeeds[config];const bounce=(config==2)?this.bounceCube():(()=>{});this.rotationTween=new Tween({easing:easing,duration:duration,onUpdate:tween=>{let deltaAngle=tween.delta*rotation;this.group.rotateOnAxis(this.flipAxis,deltaAngle);bounce(tween.value,deltaAngle,rotation);},onComplete:()=>{if(!scramble)this.onMove();const layer=this.flipLayer.slice(0);this.game.cube.object.rotation.setFromVector3(this.snapRotation(this.game.cube.object.rotation.toVector3()));this.group.rotation.setFromVector3(this.snapRotation(this.group.rotation.toVector3()));this.deselectLayer(this.flipLayer);callback(layer);},});}
bounceCube(){let fixDelta=true;return(progress,delta,rotation)=>{if(progress>=1){if(fixDelta){delta=(progress-1)*rotation;fixDelta=false;}
this.game.cube.object.rotateOnAxis(this.flipAxis,delta);}}}
rotateCube(rotation,callback){const config=this.flipConfig;const easing=[Easing.Power.Out(4),Easing.Sine.Out(),Easing.Back.Out(2)][config];const duration=[100,150,350][config];this.rotationTween=new Tween({easing:easing,duration:duration,onUpdate:tween=>{this.edges.rotateOnWorldAxis(this.flipAxis,tween.delta*rotation);this.game.cube.object.rotation.copy(this.edges.rotation);},onComplete:()=>{this.edges.rotation.setFromVector3(this.snapRotation(this.edges.rotation.toVector3()));this.game.cube.object.rotation.copy(this.edges.rotation);callback();},});}
selectLayer(layer){this.group.rotation.set(0,0,0);this.movePieces(layer,this.game.cube.object,this.group);this.flipLayer=layer;}
deselectLayer(layer){this.movePieces(layer,this.group,this.game.cube.object);this.flipLayer=null;}
movePieces(layer,from,to){from.updateMatrixWorld();to.updateMatrixWorld();layer.forEach(index=>{const piece=this.game.cube.pieces[index];piece.applyMatrix(from.matrixWorld);from.remove(piece);piece.applyMatrix(new THREE.Matrix4().getInverse(to.matrixWorld));to.add(piece);});}
getLayer(position){const layer=[];let axis;if(position===false){axis=this.getMainAxis(this.flipAxis);position=this.getPiecePosition(this.dragIntersect.object);}else{axis=this.getMainAxis(position);}
this.game.cube.pieces.forEach(piece=>{const piecePosition=this.getPiecePosition(piece);if(piecePosition[axis]==position[axis])layer.push(piece.name);});return layer;}
getPiecePosition(piece){let position=new THREE.Vector3().setFromMatrixPosition(piece.matrixWorld).multiplyScalar(3);return this.game.cube.object.worldToLocal(position.sub(this.game.cube.animator.position)).round();}
scrambleCube(){if(this.scramble==null){this.scramble=this.game.scrambler;this.scramble.callback=(typeof callback!=='function')?()=>{}:callback;}
const converted=this.scramble.converted;const move=converted[0];const layer=this.getLayer(move.position);this.flipAxis=new THREE.Vector3();this.flipAxis[move.axis]=1;this.selectLayer(layer);this.rotateLayer(move.angle,true,()=>{converted.shift();if(converted.length>0){this.scrambleCube();}else{this.........完整代码请登录后点击上方下载按钮下载查看

网友评论0