Write some note.
See issues
Write some note.
License: MIT License
插值: 通过已知离散数据求未知数据的方法。如线性插值,通过线性的比例来求未知的数据(等比例缩放)。
补间:主要形容动画制作时,定义好起始帧和结束帧(关键帧)的元素状态,然后在两关键帧之间通过不同的插值方式进行动画动作(位移、旋转、颜色等)的补充。
二阶贝塞尔曲线:至少知道3点,设3点为p0, p1, p2,以p0向p1运动的一个连续点为q1,以p1向p2运动的一个连续点为q2,这两个连续点同时出发,同时到达终点。然后以q1向q2运动的一个连续点为B,B也与q1,q2同时出发和同时到达终点。
WikiPedia
三阶贝塞尔曲线:由四点共同确定。由于三点(终点不动)的线性运动会出现两个连续点,所以以这两个连续点为二阶贝塞尔曲线的瞬间状态,求出此二阶贝塞尔曲线的连续点。
WikiPedia
Why Bezier? "为什么矢量绘图工具常用贝塞尔曲线而很少用 B 样条? - 刘志松的回答 - 知乎"
1、贝塞尔曲线有一个重要特征:第一个控制点与第二个控制点的连线恰好是第一个控制点处的切线,而最后一个控制点和倒数第二个控制点的连线恰好是最后一个控制点处的切线。这一特性使设计人员可以更方便和直接的控制曲线的切线方向。我想这也正是@覃延飞 答案中feeling所指。B样条曲线给了使用者更大的自由度,却丧失了上面的特性,也许是其很少用在绘图软件里的一个原因。
2、矢量绘图工具不需要使用B样条,是因为矢量绘图工具并不需要高精度的拟合某个数学描述的曲线。常用的矢量绘图工具(比如Illustrator)使用三次贝塞尔曲线就已经可以进行较好的艺术表现了。而作为CAD软件,对曲线曲面的描述精确度要求是远远高于矢量绘图软件的,因此使用B样条(主要以 NURBS为主)几乎是唯一选择。另外,贝塞尔曲线用于3D建模的蛋疼程度也决定了它不太可能用于CAD软件
Basicly, I try to follow http://reactnative.cn/docs to start learning react native. And now I finally done my first react-native run-android
. Not easy for me, because I have a poor computer. I will mark some problems that I had faced here.
To begin with, the tutorial in http://reactnative.cn/docs is very useful, so just do what it said until something stops you.
The first thing is about Genymotion. The website of Genymotion is blocked in China so if you need to register, you have to go through the GFW. Use your VPN or something.
After successfully installing Genymotion, you will be asked to login, This time it is not necessary to use VPN but you may fail to login. In this case, try to click more times or reopen Genymotion.
When adding a new device (downloading files for it), it may fail and stop downloading. You can just select the same API and device because it will continue from the last.
tag: Genymotion 被墙
Then it goes to react-native run-android
. The first problem is:
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring project ':app'.
> failed to find target with hash string 'android-23' in: /usr/local/Cellar/android-sdk
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
Well I had found many answers on SO but they can not fix it, because I just seached failed to find target with hash string 'android-23'
. At last I added react native
as describe and the answer came out: http://stackoverflow.com/questions/32731858/react-native-target-with-hash-string-android-x-not-found
It is because I pointed ANDROID_HOME
to the platforms
directory. The correct directory should be the parent folder of it.
Could not resolve all dependencies for configuration 'app:_debugCompile'
Could not find com.android.support
It is easy to fix this by intalling two packages in SDK Manager:
Don`t know why these two packages are not pointed out on the guide. But just install it and try again.
It takes me so much time to deal with these problems (or should blame on me cuz I am lazy). Hope this issue can help someone.
其实随着浏览器性能越来越强大,在Web端实现3D的场景或者游戏也会越来越流畅。今天跟大家分享一个小项目,通过ThreeJS实现简单的太阳系模型、恒星背景等。
预览: http://soaanyip.github.io/SolarSystem/solar
源码在 https://github.com/SoAanyip/SolarSystem , 可以直接对照着源码查看。
首先写一个简单的页面,准备一个canvas容器:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Solar System</title>
<style>
body{
margin:0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="main"></canvas>
<script src="../resource/js/three/three.js"></script>
<script src="http://localhost:8080/webpack-dev-server.js"></script>
<script src="/main.js"></script>
</body>
</html>
然后在js文件中准备好三要素:renderer, camera, scene
const canvas = document.getElementById('main');
/*画布大小*/
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/*renderer*/
renderer = new THREE.WebGLRenderer({ canvas });
renderer.shadowMap.enabled = true; //辅助线
renderer.shadowMapSoft = true; //柔和阴影
renderer.setClearColor(0xffffff, 0);
/*scene*/
scene = new THREE.Scene();
/*camera*/
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1 ,1000);
camera.position.set(-200,50,0);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
renderer.render(scene,camera);
现在我们已经准备好一个#fff
的画布,以及一个看着(0,0,0)的摄像机了。接下来,我们现在原点设置一个太阳,虽然这个太阳现在看上去只是一个橘子:
/*sun*/
const Sun = new THREE.Mesh( new THREE.SphereGeometry( 12 ,16 ,16 ),
new THREE.MeshLambertMaterial({
color: 0xffff00,
emissive: 0xdd4422
})
);
Sun.name='Sun';
scene.add(Sun);
一个太阳太孤独了,我们先把地球也放在画布上
const Earth = new THREE.Mesh( new THREE.SphereGeometry( 5, 16,16 ),
new THREE.MeshLambertMaterial( { color: 'rgb(46,69,119)', emissive: 'rgb(46,69,119)' } )
);
Earth.position.z = -40;
scene.add(Earth);
由于我们还没定义光源,所以先使用emissive来显示球体的颜色。
实际上,太阳系各个行星的轨道是不在一个平面上的。在这个例子的处理里面,我暂时不考虑这一点,假设行星的轨道都在(x, 0, z)的平面上。
接下来,就可以此类推添加上太阳系的各个行星了。这里各星体的比例和距离没有遵循实际的数据,因为实际的比例会相当难观察。
let Sun,
Mercury, //水星
Venus, //金星
Earth,
Mars,
Jupiter, //木星
Saturn, //土星
Uranus, //天王
Neptune, //海王
stars = [];
module.exports = {
init(){
//构造太阳
...
/*planets*/
Mercury = this.initPlanet('Mercury','rgb(124,131,203)',20,2);
stars.push(Mercury);
Venus = this.initPlanet('Venus','rgb(190,138,44)',30,4);
stars.push(Venus);
Earth = this.initPlanet('Earth','rgb(46,69,119)',40,5);
stars.push(Earth);
Mars = this.initPlanet('Mars','rgb(210,81,16)',50,4);
stars.push(Mars);
Jupiter = this.initPlanet('Jupiter','rgb(254,208,101)',70,9);
stars.push(Jupiter);
Saturn = this.initPlanet('Saturn','rgb(210,140,39)',100,7);
stars.push(Saturn);
Uranus = this.initPlanet('Uranus', 'rgb(49,168,218)',120,4);
stars.push(Uranus);
Neptune = this.initPlanet('Neptune','rgb(84,125,204)',150,3);
stars.push(Neptune);
},
/**
* 初始化行星
* @param name 行星名字
* @param color 颜色
* @param distance 距离原点(太阳中心)的距离
* @param volume 体积
* @returns {{name: *, distance: *, volume: *, Mesh: THREE.Mesh}}
*/
initPlanet(name,color,distance,volume) {
let mesh = new THREE.Mesh( new THREE.SphereGeometry( volume, 16,16 ),
new THREE.MeshLambertMaterial( { emissive: color } )
);
mesh.position.z = -distance;
mesh.receiveShadow = true;
mesh.castShadow = true;
mesh.name = name;
let star = {
name,
distance,
volume,
Mesh : mesh
}
scene.add(mesh);
return star;
},
}
抽象出了一个方法用于构造行星, 这里所有的星体都使用Lambert
材质。
虽然现在看上去像是在打祖玛,但是都会好起来的~
接下来我们无论想让行星动起来,还是镜头动起来,都涉及到动画的部分。Three推荐通过requestAnimationFrame
的方法进行动画,这个方法的调用方式跟setTimeout
类似:
function move() {
//do sth...
requestAnimationFrame(move)
}
move()
虽然没有指定动画的间隔, 但是这个方法默认以一秒60次(60帧)的频率执行。
requestAnimationFrame
跟setInterval
的区别主要在于CPU占用率、浏览器兼容性和卡顿处理等。一般来说,requestAnimationFrame对CPU更友好, 而由于是比较新的方法所以需要注意低版本浏览器的兼容。在卡顿处理上,当浏览器达不到设定的调用周期时,requestAnimationFrame采用跳过某些帧的方式来表现动画,虽然会有卡滞的效果但是整体速度不会拖慢,而setInterval会因此使整个程序放慢运行,但是每一帧都会绘制出来。这里涉及到拖慢和卡顿的取舍问题。
requestAnimationFrame适用于对于时间较为敏感的环境(但是动画逻辑更加复杂),而setInterval则可在保证程序的运算不至于导致延迟的情况下提供更加简洁的逻辑(无需自行处理时间)。
参考 http://creativejs.com/resources/requestanimationframe/ 和开头提到的
张雯莉的Three.js入门指南 。
要加入第一视觉移动的功能,可以选择Three提供的部件firstPersonControls。这个部件需要用script另外引入。使用方法也很简单:
let control;
const clock = new THREE.Clock(); //用于计算两次animationFrame之间间隔时间
init() {
//...
/*镜头控制*/
control = new THREE.FirstPersonControls( camera , canvas);
control.movementSpeed = 100; //镜头移速
control.lookSpeed = 0.125; //视角改变速度
control.lookVertical = true; //是否允许视角上下改变
renderer.render(scene,camera);
requestAnimationFrame(()=>this.move());
},
move() {
control.update( clock.getDelta() ); //此处传入的delta是两次animationFrame的间隔时间,用于计算速度
renderer.render(scene,camera);
requestAnimationFrame(()=>this.move());
}
firstPersonControls通过距离(鼠标移动过的屏幕距离)和时间(通过Clock计算)计算而得出镜头视觉改变的速度,相当于我们站着不动,转动眼睛、头部的视觉改变方式。而镜头本身位置的改变相当于人走路,通过按键监听实现。
现在我们已经可以进行主视觉控制镜头进行游览啦!
来看看这一串完全静止不动的冰糖葫芦:
在初步引入动画的的布置之后,我们最好引入一个帧率监视的工具,以便我们对整个动画的效率的掌握。
<script src="../resource/js/three/stats.min.js"></script>
引入后只需要在js代码初始化,并在move()
中更新即可:
init() {
/*stats帧率统计*/
/*放置dom*/
stat = new Stats();
stat.domElement.style.position = 'absolute';
stat.domElement.style.right = '0px';
stat.domElement.style.top = '0px';
document.body.appendChild(stat.domElement);
...
},
move() {
//...
stat.update();
}
我们知道,想用canvas或者Three做点高大上的东西,都离不开数学和图形学。在这一部分当然不会有很复杂的公式之类,但是也要好好的思考一下:
我们需要让星球环绕着太阳(原点)做圆周运动,在只需要考虑平面(x, 0, z)的情况下,实际就是通过三角函数去计算星球的平面位置。
(图自维基百科)
我们设置(0, 0)为太阳中心位置,P点(x, y)则为星体位置。当然了,y值会在实际中作为z的值。
我们给星体设置一个公转的角速度, 每次animationFrame的执行中,我们都为星体累加角度, 通过Math.sin(), Math.cos()即可顺利计算出星体当前的位置。
/*每一颗行星的公转*/
moveEachStar(star){
star.angle+=star.speed;
if (star.angle > Math.PI * 2) {
star.angle -= Math.PI * 2;
}
star.Mesh.position.set(star.distance * Math.sin(star.angle), 0, star.distance * Math.cos(star.angle));
}
其中,我们要给每一个星体加上当前角度和角速度的属性。当角度已经累加到2PI时,此时星体已经走过一圈了,所以可以把无用的2PI去掉。由于动画大概每秒60帧,所以每秒钟大概会累加60*speed。这个方法在move()
中为每一个星体都执行一次,就可以真正动起来了。
init() {
//...
/*角速度为0.02,初始角度为0*/
Mercury = this.initPlanet('Mercury',0.02,0,'rgb(124,131,203)',20,2);
...
}
initPlanet(name,speed,angle,color,distance,volume,ringMsg) {
//...
let star = {
name,
speed,
angle,
distance,
volume,
Mesh : mesh
}
}
到现在,整个游览太阳系的模型已经出来了。
为了方便观察,使用RingGeometry来制作运动轨迹:
initPlanet() {
/*轨道*/
let track = new THREE.Mesh( new THREE.RingGeometry (distance-0.2, distance+0.2, 64,1),
new THREE.MeshBasicMaterial( { color: 0x888888, side: THREE.DoubleSide } )
);
track.rotation.x = - Math.PI / 2;
scene.add(track);
}
由于Ring默认是垂直于x轴,需要让它进行一次rotate。
接下来要开始设置光源相关。在这个太阳系的环境中,我们需要用到环境光和点光。PointLight自不必说,把它放在太阳的中心来模拟太阳发出的亮光,大写的一个太阳能电灯泡。而行星的背面由于不会被太阳光照到,需要环境光AmbientLight来辅助照明。
//环境光
let ambient = new THREE.AmbientLight(0x999999);
scene.add(ambient);
/*太阳光*/
let sunLight = new THREE.PointLight(0xddddaa,1.5,500);
scene.add(sunLight);
其中PointLight的后两个参数代表光照强度和光照影响的距离。接收第三个参数的话就代表光照衰减,
?,是不是感觉不太对?
因为我们之前在构造星体的时候,赋予颜色的属性值是emissive,只需要改回来color即可。
一个大黄色的咸蛋黄的确不太好看,我们要做的是给太阳加上图片作为材质。
/*sun skin pic*/
let sunSkinPic = THREE.ImageUtils.loadTexture('../resource/img/sunCore.jpg', {}, function() {
renderer.render(scene, camera);
});
/*sun*/
Sun = new THREE.Mesh( new THREE.SphereGeometry( 12 ,16 ,16 ),
new THREE.MeshLambertMaterial({
/*color: 0xffff00,*/
emissive: 0xdd4422,
map: sunSkinPic
})
);
使用THREE.ImageUtils.loadTexture
可以加载一个图片作为材质。这一张图片是这样子的:
当我们使用它作为球体的材质时,它会自动进行完全的覆盖。
太阳有了皮肤之后,会很明显发现太阳根本就没有自转。我们可以在move
中改变它的rotation.y
来让它跑得比谁都快。
/*太阳自转*/
Sun.rotation.y = (Sun.rotation.y == 2*Math.PI ? 0.0008*Math.PI : Sun.rotation.y+0.0008*Math.PI);
虽然Three为我们做了很好的优化工作,但是如果我们想for出几万个SphereGeometry做背景的星光,电脑肯定就要跪在地上了。如何合理运用官方的API来做出这一点?
答案就是BufferGeometry。BufferGeometry会保存这个Geometry所有的数据,including vertex positions, face indices, normals, colors, UVs, and custom attributes within buffers,从而大量减少GPU的运算压力。
check http://threejs.org/docs/#Reference/Core/BufferGeometry . 但是相对的,使用难度也稍微大一点。
/*背景星星*/
const particles = 20000; //星星数量
/*buffer做星星*/
const bufferGeometry = new THREE.BufferGeometry();
/*32位浮点整形数组*/
let positions = new Float32Array( particles * 3 );
let colors = new Float32Array( particles * 3 );
let color = new THREE.Color();
const gap = 1000; // 定义星星的最近出现位置
首先进行准备工作。每一个Float32Array数组的每三个成员来确定一个行星的信息(位置、颜色)。
for ( let i = 0; i < positions.length; i += 3 ) {
// positions
/*-2gap < x < 2gap */
let x = ( Math.random() * gap *2 )* (Math.random()<.5? -1 : 1);
let y = ( Math.random() * gap *2 )* (Math.random()<.5? -1 : 1);
let z = ( Math.random() * gap *2 )* (Math.random()<.5? -1 : 1);
/*找出x,y,z中绝对值最大的一个数*/
let biggest = Math.abs(x) > Math.abs(y) ? Math.abs(x) > Math.abs(z) ? 'x' : 'z' :
Math.abs(y) > Math.abs(z) ? 'y' : 'z';
let pos = { x, y, z};
/*如果最大值比n要小(因为要在一个距离之外才出现星星)则赋值为n(-n)*/
if(Math.abs(pos[biggest]) < gap) pos[biggest] = pos[biggest] < 0 ? -gap : gap;
x = pos['x'];
y = pos['y'];
z = pos['z'];
positions[ i ] = x;
positions[ i + 1 ] = y;
positions[ i + 2 ] = z;
// colors
}
要定义一颗星星的位置,首先我们要决定它能够出现的范围。在这里我简单的设置为:以原点为中心,在边长为2000的立方体内不能出现星星,边长为4000的立方体外不能出现,类似于一个空心的立方体。太近的话容易被镜头撞上,太远了镜头也很难捕捉。
在这种情况下,x, y, z三个坐标必须至少有一个坐标是比gap(1000)要大的,这样才能保证不会出现在内层。
// colors
/*70%星星有颜色*/
let hasColor = Math.random() > 0.3;
let vx, vy, vz;
if(hasColor){
vx = (Math.random()+1) / 2 ;
vy = (Math.random()+1) / 2 ;
vz = (Math.random()+1) / 2 ;
}else{
vx = 1 ;
vy = 1 ;
vz = 1 ;
}
color.setRGB( vx, vy, vz );
colors[ i ] = color.r;
colors[ i + 1 ] = color.g;
colors[ i + 2 ] = color.b;
接下来在for循环中为星星添上颜色。color.setRGB会把01的数字转化为0255的rgb色,这里我选择范围为0.5~1的随机值是因为亮色的星星会比较好看。
bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
bufferGeometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
bufferGeometry.computeBoundingSphere();
/*星星的material*/
let material = new THREE.PointsMaterial( { size: 6, vertexColors: THREE.VertexColors } );
particleSystem = new THREE.Points( bufferGeometry, material );
scene.add( particleSystem );
最后设置bufferGeometry, 并让它进行空间计算。
看着终于有一点宇宙的感觉了。在移动镜头时,也能感觉到星星的空间层次感。至此,整个项目已经完成绝大部分了,最后就是收尾的工作。
太阳系行星傻傻分不清楚。这一部分可以让鼠标指向的行星的名字显示出来。
const raycaster = new THREE.Raycaster(); //指向镭射
const mouse = new THREE.Vector2(); //鼠标屏幕向量
这里引入两个对象,辅助我们对鼠标指向的对象进行捕捉。
在move()
中加入判断交汇对象的部分:
/*鼠标指向行星显示名字*/
raycaster.setFromCamera( mouse, camera );
/*交汇点对像*/
let intersects = raycaster.intersectObjects( scene.children );
if( intersects.length > 0 ){
/*取第一个交汇对像(最接近相机)*/
let obj = intersects[ 0 ].object;
let name = obj.name;
}
通过raycaster可以取得鼠标指向的,离屏幕(镜头)最近的一个对象。
要想显示出名字,可以使用TextGeometry
let planetName = new THREE.Mesh(
new THREE.TextGeometry( name, {
size: 4,
height: 4
}),
new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide} )
);
通过传入文字和大小信息,就可以在空间中显示出立体的文字。关于文字出现的位置可以这样设置:
/*复制行星位置*/
displayName.position.copy(obj.position);
/*文字居中*/
displayName.geometry.center();
/*显示在行星的上方(y轴)*/
displayName.position.y = starNames[name].volume + 4;
/*面向相机*/
displayName.lookAt(camera.position);
详细代码请参考源码。
上述没说到的内容,还有土星的圆环、太阳的模拟的燃烧层、限制镜头移动空间等,这些都可以轻松的在源码中找到。
最后祝大家身体健康
开始学习移动端开发的时候,首先想到的就是搭建多端同步测试工具,于是便捣鼓Browsersync的使用。
在前面的配置步骤都没有遇到问题,但是当启动Browsersync服务器的时候,手机无法通过External的ip访问。
看到别人出现同样的问题的原因是可能配置了VPN或者代理,但是我也并没有使用这两个的印象。
后来在IE的连接 -> 代理服务器看到了一个CHINANETSNWide的项目,原来是在使用校园网NetKeeper的时候自动生成的一个选项。只要把这个选项删除了,Browsersync就可以生成正确的External url 访问。
check 移动端真机调试终极利器-BrowserSync
http://soaanyip.github.io/mobile/hear-site/index.html
BorwserSync + Zepto + FrozenUI
首先是多终端同步调试的browserSync的确很有用,一些bug在自己的古老旧版Android机上才有发现出来。
在布局上面本来尝试原生的flex,但是自己手机(Android4.1?)不支持,于是布局使用frozen兼容的flex的class。
多行文本截断使用 .ui-nowrap-multi
,通过调整-webkit-line-clamp
属性控制行数。
story中展开的效果是transition
,毫无意外在自己手机上十分的卡。
记录了tap展开时的scroll位置,收起时恢复。
top-nav需要判断机器是否支持fix,不支持直接static。
//TODO
参考frozen中的动画实现方式。
文件结构。
丰富内容。
createObjectURL is synchronously executed
filereader.readasdataurl is asynchronously executed (works with callback)
createObjectURL return url with hash, and store object in memory until document triggers unload event (document close) or execute revokeObjectURL
filereader.readasdataurl return base64, that contains many characters, and use more memory than blob url, but removes from memory when you don't use it (by garbage collector)
createObjectURL from 10 ie and all modern browsers
filereader.readasdataurl from 10 ie and all modern browsers
CreateObjectURL is more efficient and faster, but if you use many object urls, you need to release urls by revokeObjectURL (for free memmory)
http://stackoverflow.com/questions/31742072/filereader-vs-window-url-createobjecturl
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.