3D 动画

博客分类: 江河计划

3D 动画

算法

有效数独 判断一个 9x9 的数独是否有效。

    var isValidSudoku = function(board) {
        var pieceMap = {};
            columnsMap = {}
        var res = board.some((arr, index1) => {
            var numMap = {};
            return arr.some((item, index2) => {
                pieceKey = Math.floor(index1/3) + '' + Math.floor(index2/3);
                if(!pieceMap[pieceKey]){
                    pieceMap[pieceKey] = {}
                }
                if(item === '.'){
                    return false
                }
                if(numMap[item]){
                    return true;
                }else{
                    numMap[item] = true;
                }
                if(pieceMap[pieceKey][item]){
                    return true;
                }else{
                    pieceMap[pieceKey][item] = true;
                }
                if(!columnsMap[index2]){
                    columnsMap[index2] = {};
                }
                if(columnsMap[index2][item]){
                   return true 
                }else{
                    columnsMap[index2][item] = true;
                }
            })
        });
        if(res){
            return false
        }
        return true
    };

n*n 数组矩阵旋转

    [
      [ 5, 1, 9,11],
      [ 2, 4, 8,10],
      [13, 3, 6, 7],
      [15,14,12,16]
    ]
    // 转换成 
    [
      [15,13, 2, 5],
      [14, 3, 4, 1],
      [12, 6, 8, 9],
      [16, 7,10,11]
    ]
    
    const rotate = (matrix) => {
        matrix.reverse()
        for (let i = 0; i < matrix.length; i++) {
            for (let j = i + 1; j < matrix[0].length; j++) {
                let tmp = matrix[i][j]
                matrix[i][j] = matrix[j][i]
                matrix[j][i] = tmp
            }
        }
    };

3D 动画

canvas.getContext(contextType, contextAttributes);

// canvas 获取绘图上下文
var context = canvas.getContext('2d');
// WebGL 获取绘图上下文
var gl = canvas.getContext('webgl') // 或 experimental-webgl

Canvas 是浏览器封装好的一个绘图环境,在实际进行绘图操作时,浏览器仍然需要调用 OpenGL API。而 WebGL API 几乎就是 OpenGL API 未经封装,直接套了一层壳,threejs 是一个 webGL 框架。

webGL 整体就是用来在 canvas 上绘制 3D 效果的 API。目前封装的比较好的是 threejs。

首先一个 3D 动画由这么几部分元素组成。容器、舞台、演员、天空、相机、控制器等

容器

动画需要有个承载的体现,这个我们一般称为容器,在这里我们一般认为 canvas 标签就是容器。这个容器是用来展示动画的载体。整个容器可以认为是一个无边无际的三维坐标系,并且有正有负。

舞台

舞台就是一个动画的场景,如果我们认为动画是一个 3D 的场景,一个小的动画只用一个场景就可以,但是一个复杂的大型的动画是由一个一个场景组合起来的。每个场景又有自己的动画,区分场景是为了将大的动画分成几组来完成,降低复杂度。

一个舞台就是在这个无边无际的坐标系中的其中一块立体空间。

演员

有了舞台就需要演员来完成动画,动画场景中的每一个动画元素都可以称为一个演员,演员也可以分组,一个复杂的演员可以由多个小的动画元素组成,而每一个动画元素也可以称为一个演员,主要是看其相关性,将演员分组也是降低复杂度的一种。

演员基本上就是场景中的动画元素了,一个复杂的场景动画演员有自己的运动轨迹,演员在场景中完成自己的动画轨迹就完成了自己的动画,如果演员的运动轨迹是由其他因素所决定的,那就根据自己的轨迹去完成。

天空

我们可以理解为整个 3D 动画的场景是被放在一个立体的盒子中的,天空就是盒子内部的内表皮,盒子内部的六个面都可以有自己的颜色图片,我们在盒子内部的舞台中,看起来这个就像是动画的天空。

当然外表皮也是可以着色的,但我们一般是把相机放在舞台内部,所有外表皮的意义并不大。

相机

这个是非常重要的,与其说相机不如说是镜头摆放的位置,threejs 中有两种摄像机方式,正交投影和透视投影两种,最好自己有过实际的 demo 来体会一下两种相机的不同。

相机摆放在不同的位置就能看到不同角度位置的动画,在立体坐标系中,将相机让在某一个舞台面前就能看到这个舞台上的动画。主要是看动画想以哪种形式进行展现,然后设置好相机的位置。动画场景的切换也是通过移动相机,将摄像机放到下一个场景的位置。

控制器

一般情况下我们动画只是播放,很少有交互,简单的交互点击,拖动这些是不用设计到控制器的。控制器是控制动画展现的位置的东西,其实就是控制摄像头的摆放,根据不同的交互类型让摄像头按照某种规律进行运动,主要用于游戏中的交互,可以自行写个 demo 试试每种控制器都有什么方式,分为下面几类:

  • OrbitControls:旋转控制器
  • FlyControls:飞行控制器
  • PointerLockControls:指针控制器
  • TrackballControls:轨迹球控制器
  • TransformControls:变换物体控制器

动画刷新

动画实际上是由一帧一帧的静态图片连贯组合出来的,也就是说我们想要看到动画,需要不断的刷新页面,然后展现当前动画场景的不同状态,不断的变化的状态连贯起来就是动画了。fps 就不说了,动画是需要 requestAnimationFrame 不断执行,每次执行都去 render 当前动画的状态。

实例

现在动画的实际讲解还是很少,很多都要去翻看很多资料才能收获一点点知识,包括我自己也是一知半解,能做出实际的动画 demo,也做过几个线上的大型场景动画,但是自己要去建模设计还是不是很懂的。下面是相关这些讲解的实际例子。

demo

var THREE = require('three');
var Stats = require('stats-js');
var OrbitControls = require('three-orbitcontrols')
require('three-fly-controls')(THREE);
var PointerLockControls = require('three-pointerlock');
var TrackballControls = require('three-trackballcontrols');
var TransformControls = require('three-transformcontrols');

var renderer;
var stats;
var width;
var height;

function initThree() {
    width = document.getElementById('canvas-frame').clientWidth;
    height = document.getElementById('canvas-frame').clientHeight;
    renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    renderer.shadowMapEnabled = true;
    renderer.shadowMapSoft = true;
    // 初始化容器
    renderer.setSize(width, height);
    document.getElementById('canvas-frame').appendChild(renderer.domElement);
    renderer.setClearColor(0xFFFFFF, 1.0);
    // 监控
    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.left = '0px';
    stats.domElement.style.top = '0px';
    document.getElementById('canvas-frame').appendChild(stats.domElement);
}

var camera;

function initCamera() {
    camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
    camera.position.x = 100;
    camera.position.y = 200;
    camera.position.z = 800;
    camera.up.x = 0;
    camera.up.y = 1;
    camera.up.z = 0;
    camera.lookAt({
        x: 0,
        y: 0,
        z: 0
    });
    // camera = new THREE.OrthographicCamera(-300, 300, 300, -300, 1, 1000);
    // camera.position.set(0, 200, 800);
    // camera.lookAt({
    //     x : 0,
    //     y : 0,
    //     z : 0
    // });
}

var scene;

function initScene() {
    scene = new THREE.Scene();
}

var light;

function initLight() {
    light = new THREE.AmbientLight(0xFF0000);
    light.position.set(100, 100, 200);

    scene.add(light);
}

var cube;
var mesh;

function initObject() {

    var geometry = new THREE.BoxGeometry(100, 100, 100);

    for (var i = 0; i < geometry.faces.length; i += 2) {

        var hex = Math.random() * 0xffffff;
        geometry.faces[i].color.setHex(hex);
        geometry.faces[i + 1].color.setHex(hex);

    }
    var material = new THREE.MeshBasicMaterial({
        vertexColors: THREE.FaceColors
    });
    mesh = new THREE.Mesh(geometry, material);


    // mesh.position = new THREE.Vector3(0,0,0);
    scene.add(mesh);

    // 天空
    var skyGeometry = new THREE.CubeGeometry(9000, 9000, 9000);
    var materialArray = [];
    for (var i = 0; i < 6; i++)
        materialArray.push(new THREE.MeshBasicMaterial({
            map: THREE.ImageUtils.loadTexture('http://fe.benmu-health.com/createjsDemo/images/15.jpg'),
            side: THREE.BackSide
        }));
    var skyMaterial = new THREE.MeshFaceMaterial(materialArray);
    var skyBox = new THREE.Mesh(skyGeometry, skyMaterial);
    scene.add(skyBox);


}

function initGrid() {
    var helper = new THREE.GridHelper(1000, 50, 0x0000ff, 0x808080);
    scene.add(helper);
}

function initActor() {
    var aaa = new THREE.CubeGeometry(100, 100, 100);
    var bbb = new THREE.MeshBasicMaterial({
        map: texture
    });
    var cube = new THREE.Mesh(aaa, bbb);

    cube.position.x = -200
    scene.add(cube);
}

function threeStart() {
    initThree();
    initCamera();
    initScene();
    initLight();


    initObject();
    initGrid();
    initActor();
    renderer.render(scene, camera);

    // 旋转控制器 类似按照某一个舞台进行旋转
    // controls = new OrbitControls(camera, renderer.domElement)
    // controls.enableDamping = true
    // controls.dampingFactor = 1
    // 飞行控制器 飞行游戏
    // controls = new THREE.FlyControls(camera, renderer.domElement);
    // 指针控制器 https://threejs.org/examples/misc_controls_pointerlock.html
    // controls = new PointerLockControls(camera)
    // scene.add(controls.getObject());
    // move();
    // 轨迹球控制器
    // controls = new TrackballControls(camera, renderer.domElement);
    // 变换物体控制器
    // controls = new TransformControls(camera, renderer.domElement);
    // controls.attach(mesh);
    // scene.add(controls);
    window.addEventListener('keydown', function (event) {
        switch (event.keyCode) {
            case 81: // Q
                controls.setSpace(controls.space === "local" ? "world" : "local");
                break;
            case 17: // Ctrl
                controls.setTranslationSnap(100);
                controls.setRotationSnap(THREE.Math.degToRad(15));
                break;
            case 87: // W
                controls.setMode("translate");
                break;
            case 69: // E
                controls.setMode("rotate");
                break;
            case 82: // R
                controls.setMode("scale");
                break;
            case 187:
            case 107: // +, =, num+
                controls.setSize(controls.size + 0.1);
                break;
            case 189:
            case 109: // -, _, num-
                controls.setSize(Math.max(controls.size - 0.1, 0.1));
                break;
        }
    });
    animation();
}
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;
var canJump = false;
var raycaster;
var prevTime = performance.now();
var velocity = new THREE.Vector3();

function move() {
    var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
    document.addEventListener('pointerlockchange', changeCallback, false);
    document.addEventListener('mozpointerlockchange', changeCallback, false);
    document.addEventListener('webkitpointerlockchange', changeCallback, false);

    function changeCallback() {
        if (document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element) {
            controls.enabled = true;
        } else {
            controls.enabled = false;
        }
    }
    var element = document.body;
    element.addEventListener('click', () => {
        element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
        element.requestPointerLock();
    })
    var onKeyDown = function (event) {
        switch (event.keyCode) {

            case 38: // up
            case 87: // w
                moveForward = true;
                break;

            case 37: // left
            case 65: // a
                moveLeft = true;
                break;

            case 40: // down
            case 83: // s
                moveBackward = true;
                break;

            case 39: // right
            case 68: // d
                moveRight = true;
                break;

            case 32: // space
                if (canJump === true) velocity.y += 350;
                canJump = false;
                break;

        }

    };

    var onKeyUp = function (event) {

        switch (event.keyCode) {

            case 38: // up
            case 87: // w
                moveForward = false;
                break;

            case 37: // left
            case 65: // a
                moveLeft = false;
                break;

            case 40: // down
            case 83: // s
                moveBackward = false;
                break;

            case 39: // right
            case 68: // d
                moveRight = false;
                break;

        }

    };

    document.addEventListener('keydown', onKeyDown, false);
    document.addEventListener('keyup', onKeyUp, false);

    raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 10);
}

var controls;
var xxx = 1
// 帧循环、游戏循环
function animation() {
    // mesh.rotation.x +=0.01;
    // scene.rotation.x += 0.01;
    // scene.rotation.y += 0.01;
    // scene.rotation.z += 0.01;
    // mesh.rotation.y +=0.01;
    // mesh.rotation.z +=0.01;
    // if(mesh.position.x > 100){
    //     xxx = -1;
    // }
    // if(mesh.position.x < 0){
    //     xxx = 1;
    // }
    // mesh.position.x += xxx;
    // camera.position.y += xxx;
    // camera.position.x += xxx;
    // camera.position.z += xxx;

    // 指针控制器
    // raycaster.ray.origin.copy( controls.getObject().position );
    // raycaster.ray.origin.y -= 10;

    // var time = performance.now();
    // var delta = ( time - prevTime ) / 1000;

    // velocity.x -= velocity.x * 10.0 * delta;
    // velocity.z -= velocity.z * 10.0 * delta;

    // velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass

    // if ( moveForward ) velocity.z -= 400.0 * delta;
    // if ( moveBackward ) velocity.z += 400.0 * delta;

    // if ( moveLeft ) velocity.x -= 400.0 * delta;
    // if ( moveRight ) velocity.x += 400.0 * delta;

    // controls.getObject().translateX( velocity.x * delta );
    // controls.getObject().translateY( velocity.y * delta );
    // controls.getObject().translateZ( velocity.z * delta );

    // if ( controls.getObject().position.y < 10 ) {
    //     velocity.y = 0;
    //     controls.getObject().position.y = 10;
    //     canJump = true;
    // }

    // prevTime = time;

    controls.update();
    renderer.render(scene, camera);
    requestAnimationFrame(animation);

}
var texture;
// var loader = new THREE.gITFLoader();
// loader.load('http://fe.benmu-health.com/benmu-health-img/app-benmu-health/apploading.gif', (obj) => {
//     texture = obj;
//     debugger
//     threeStart()
// })

var texture = THREE.ImageUtils.loadTexture('http://fe.benmu-health.com/benmu-health-img/app-benmu-health/apploading.gif', {}, function () {
    threeStart()
});