HTML+CSS 掷骰子源码

图片[1]-HTML+CSS 掷骰子源码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>自己用自己的骰子</title>
    <style>
      html,body{padding:0;margin:0;}body{height:100vh;display:flex;align-items:end;justify-content:center;}.container{width:100%;height:100%;background-image:linear-gradient(#6dd5fa,#2980b9);}.lil-gui{--width:450px;max-width:90%;--widget-height:20px;font-size:15px;--input-font-size:15px;--padding:10px;--spacing:10px;--slider-knob-width:5px;--background-color:rgba(5,0,15,0.8);--widget-color:rgba(255,255,255,0.3);--focus-color:rgba(255,255,255,0.4);--hover-color:rgba(255,255,255,0.5);--font-family:monospace;}</style>
  </head>
  <body>
    <div class="container">
      <canvas id="canvas"></canvas>
    </div>
    <script type="importmap">{"imports":{"three":"https://unpkg.com/three@0.164.0/build/three.module.js","three/addons/":"https://unpkg.com/three@0.164.0/examples/jsm/"}}</script><script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script><script type="module">import*as CANNON from'https://cdn.skypack.dev/cannon-es';import*as THREE from'three';import*as BufferGeometryUtils from'three/addons/utils/BufferGeometryUtils.js';import GUI from'https://cdn.jsdelivr.net/npm/lil-gui@0.18.2/+esm';const containerEl=document.querySelector('.container');const canvasEl=document.querySelector('#canvas');let renderer,scene,camera,diceMesh,physicsRender,simulation;let simulationOn=true;let currentResult=[0,0];const params={segments:40,edgeRadius:0.08,notchRadius:0.15,notchDepth:0.17,restitution:0.3,friction:0.1,desiredResult:7,throw:throwMe,};function throwMe(){simulationOn=true;throwDice();}const diceArray=[];const floorPlanesArray=[];let throwBtn;initPhysics();initScene();createFloor();diceMesh=createDiceMesh();for(let i=0;i<2;i++){diceArray.push(createDice());addDiceEvents(diceArray[i],i)}createControls();throwMe();render();window.addEventListener('resize',updateSceneSize);window.addEventListener('click',()=>{});function initScene(){renderer=new THREE.WebGLRenderer({alpha:true,antialias:true,canvas:canvasEl,});renderer.shadowMap.enabled=true;renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));scene=new THREE.Scene();camera=new THREE.PerspectiveCamera(45,containerEl.clientWidth/containerEl.clientHeight,0.1,100);camera.position.set(0,9,12);camera.lookAt(0,4,0);updateSceneSize();const ambientLight=new THREE.AmbientLight(0xffffff,1);scene.add(ambientLight);const light=new THREE.PointLight(0xffffff,1000);light.position.set(10,20,5);light.castShadow=true;light.shadow.mapSize.width=2048;light.shadow.mapSize.height=2048;scene.add(light)}function initPhysics(){const gravity=new CANNON.Vec3(0,-50,0);const allowSleep=true;physicsRender=new CANNON.World({allowSleep,gravity,});simulation=new CANNON.World({allowSleep,gravity,});physicsRender.defaultContactMaterial.restitution=params.restitution;simulation.defaultContactMaterial.restitution=params.restitution;physicsRender.defaultContactMaterial.friction=params.friction;simulation.defaultContactMaterial.friction=params.friction}function createFloor(){for(let i=0;i<4;i++){const body=new CANNON.Body({type:CANNON.Body.STATIC,shape:new CANNON.Plane(),});physicsRender.addBody(body);simulation.addBody(body);let mesh;if(i===0){mesh=new THREE.Mesh(new THREE.PlaneGeometry(100,100,100,100),new THREE.ShadowMaterial({opacity:0.1,}));scene.add(mesh);mesh.receiveShadow=true}floorPlanesArray.push({body,mesh,})}floorPositionUpdate()}function floorPositionUpdate(){floorPlanesArray.forEach((f,fIdx)=>{if(fIdx===0){f.body.position.y=0;f.body.quaternion.setFromEuler(-0.5*Math.PI,0,0)}else if(fIdx===1){f.body.quaternion.setFromEuler(0,0.5*Math.PI,0);f.body.position.x=(-6*containerEl.clientWidth)/containerEl.clientHeight}else if(fIdx===2){f.body.quaternion.setFromEuler(0,-0.5*Math.PI,0);f.body.position.x=(6*containerEl.clientWidth)/containerEl.clientHeight}else if(fIdx===3){f.body.quaternion.setFromEuler(0,Math.PI,0);f.body.position.z=3}if(f.mesh){f.mesh.position.copy(f.body.position);f.mesh.quaternion.copy(f.body.quaternion)}})}function createDiceMesh(){const boxMaterialOuter=new THREE.MeshStandardMaterial({color:0xffffff,});const boxMaterialInner=new THREE.MeshStandardMaterial({color:0x000000,roughness:0,metalness:1,});const g=new THREE.Group();const innerSide=1-params.edgeRadius;const innerMesh=new THREE.Mesh(new THREE.BoxGeometry(innerSide,innerSide,innerSide),boxMaterialInner);const outerMesh=new THREE.Mesh(createBoxGeometry(),boxMaterialOuter);outerMesh.castShadow=true;g.add(innerMesh,outerMesh);return g}function createDice(){const mesh=diceMesh.clone();scene.add(mesh);const shape=new CANNON.Box(new CANNON.Vec3(0.5,0.5,0.5));const mass=1;const sleepTimeLimit=0.02;const body=new CANNON.Body({mass,shape,sleepTimeLimit,});physicsRender.addBody(body);const simulationBody=new CANNON.Body({mass,shape,sleepTimeLimit,});simulation.addBody(simulationBody);return{mesh,body:[body,simulationBody],startPos:[null,null,null],}}function createBoxGeometry(){let boxGeometry=new THREE.BoxGeometry(1,1,1,params.segments,params.segments,params.segments);const positionAttr=boxGeometry.attributes.position;const subCubeHalfSize=0.5-params.edgeRadius;const notchWave=(v)=>{v=(1/params.notchRadius)*v;v=Math.PI*Math.max(-1,Math.min(1,v));return params.notchDepth*(Math.cos(v)+1)};const notch=(pos)=>notchWave(pos[0])*notchWave(pos[1]);for(let i=0;i<positionAttr.count;i++){let position=new THREE.Vector3().fromBufferAttribute(positionAttr,i);const subCube=new THREE.Vector3(Math.sign(position.x),Math.sign(position.y),Math.sign(position.z)).multiplyScalar(subCubeHalfSize);const addition=new THREE.Vector3().subVectors(position,subCube);if(Math.abs(position.x)>subCubeHalfSize&&Math.abs(position.y)>subCubeHalfSize&&Math.abs(position.z)>subCubeHalfSize){addition.normalize().multiplyScalar(params.edgeRadius);position=subCube.add(addition)}else if(Math.abs(position.x)>subCubeHalfSize&&Math.abs(position.y)>subCubeHalfSize){addition.z=0;addition.normalize().multiplyScalar(params.edgeRadius);position.x=subCube.x+addition.x;position.y=subCube.y+addition.y}else if(Math.abs(position.x)>subCubeHalfSize&&Math.abs(position.z)>subCubeHalfSize){addition.y=0;addition.normalize().multiplyScalar(params.edgeRadius);position.x=subCube.x+addition.x;position.z=subCube.z+addition.z}else if(Math.abs(position.y)>subCubeHalfSize&&Math.abs(position.z)>subCubeHalfSize){addition.x=0;addition.normalize().multiplyScalar(params.edgeRadius);position.y=subCube.y+addition.y;position.z=subCube.z+addition.z}const offset=0.23;if(position.y===0.5){position.y-=notch([position.x,position.z])}else if(position.x===0.5){position.x-=notch([position.y+offset,position.z+offset]);position.x-=notch([position.y-offset,position.z-offset])}else if(position.z===0.5){position.z-=notch([position.x-offset,position.y+offset]);position.z-=notch([position.x,position.y]);position.z-=notch([position.x+offset,position.y-offset])}else if(position.z===-0.5){position.z+=notch([position.x+offset,position.y+offset]);position.z+=notch([position.x+offset,position.y-offset]);position.z+=notch([position.x-offset,position.y+offset]);position.z+=notch([position.x-offset,position.y-offset])}else if(position.x===-0.5){position.x+=notch([position.y+offset,position.z+offset]);position.x+=notch([position.y+offset,position.z-offset]);position.x+=notch([position.y,position.z]);position.x+=notch([position.y-offset,position.z+offset]);position.x+=notch([position.y-offset,position.z-offset])}else if(position.y===-0.5){position.y+=notch([position.x+offset,position.z+offset]);position.y+=notch([position.x+offset,position.z]);position.y+=notch([position.x+offset,position.z-offset]);position.y+=notch([position.x-offset,position.z+offset]);position.y+=notch([position.x-offset,position.z]);position.y+=notch([position.x-offset,position.z-offset])}positionAttr.setXYZ(i,position.x,position.y,position.z)}boxGeometry.deleteAttribute('normal');boxGeometry.deleteAttribute('uv');boxGeometry=BufferGeometryUtils.mergeVertices(boxGeometry);boxGeometry.computeVertexNormals();return boxGeometry}function addDiceEvents(dice,diceIdx){dice.body.forEach((b)=>{b.addEventListener('sleep',(e)=>{b.allowSleep=false;if(simulationOn){const euler=new CANNON.Vec3();e.target.quaternion.toEuler(euler);const eps=0.1;let isZero=(angle)=>Math.abs(angle)<eps;let isHalfPi=(angle)=>Math.abs(angle-0.5*Math.PI)<eps;let isMinusHalfPi=(angle)=>Math.abs(0.5*Math.PI+angle)<eps;let isPiOrMinusPi=(angle)=>Math.abs(Math.PI-angle)<eps||Math.abs(Math.PI+angle)<eps;if(isZero(euler.z)){if(isZero(euler.x)){currentResult[diceIdx]=1}else if(isHalfPi(euler.x)){currentResult[diceIdx]=4}else if(isMinusHalfPi(euler.x)){currentResult[diceIdx]=3}else if(isPiOrMinusPi(euler.x)){currentResult[diceIdx]=6}else{b.allowSleep=true;throwDice();}}else if(isHalfPi(euler.z)){currentResult[diceIdx]=2}else if(isMinusHalfPi(euler.z)){currentResult[diceIdx]=5}else{b.allowSleep=true;throwDice();}const thisDiceRes=currentResult[diceIdx];const anotherDiceRes=currentResult[diceIdx?0:1];const currentSum=currentResult.reduce((a,v)=>a+v,0);if(anotherDiceRes===0&&thisDiceRes>=params.desiredResult){throwDice();}else if(anotherDiceRes!==0){if(params.desiredResult!==currentSum){throwDice();}else{simulationOn=false;throwBtn.innerHTML='throw!';throwDice();}}}})})}function render(){if(simulationOn){simulation.step(1/60,5000,60)}else{physicsRender.fixedStep();for(const dice of diceArray){dice.mesh.position.copy(dice.body[0].position);dice.mesh.quaternion.copy(dice.body[0].quaternion)}renderer.render(scene,camera)}requestAnimationFrame(render)}function updateSceneSize(){camera.aspect=containerEl.clientWidth/containerEl.clientHeight;camera.updateProjectionMatrix();renderer.setSize(containerEl.clientWidth,containerEl.clientHeight);floorPositionUpdate()}function throwDice(){const quaternion=new THREE.Quaternion();if(simulationOn){throwBtn.innerHTML='calculating a throw...';currentResult=[0,0];diceArray.forEach((d)=>{d.startPos=[Math.random(),Math.random(),Math.random()]})}diceArray.forEach((d,dIdx)=>{quaternion.setFromEuler(new THREE.Euler(2*Math.PI*d.startPos[0],0,2*Math.PI*d.startPos[1]));const force=6+3*d.startPos[2];const b=simulationOn?d.body[1]:d.body[0];b.position=new CANNON.Vec3(3,5+dIdx,2);b.velocity.setZero();b.angularVelocity.setZero();b.applyImpulse(new CANNON.Vec3(-force,force,0),new CANNON.Vec3(0,0,-0.5));b.quaternion.copy(quaternion);b.allowSleep=true})}function createControls(){const gui=new GUI();gui.add(params,'desiredResult',2,12,1).name('result');const btnControl=gui.add(params,'throw').name('throw!');throwBtn=btnControl.domElement.querySelector('button > .name');}</script>
  </body>
</html>
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
相关推荐
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容