我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。
什么是elasticsearch?
一个搜素服务器,它提供了一个分布式多用户能力的全文搜索引擎 ,也可以说是一个数据库.基于RESTful web接口,也可以作为数据库使用 。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算 中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
elasticsearch特点
分布式实时文档存储,并将每一个字段都编入索引,使其可以被搜索。
实时分析的分布式搜索引擎。
可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
应用场景
可以作为一个搜素的插件应用到项目中
elasticsearch组织架构
elasticsearch中文档,类型,索引的关系
①elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档;
自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value
可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的
灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。
文档是无模式的,也就是说,字段对应值的类型可以是不限类型的。
②类型是文档的容器,就像mysql一样,表是行的容器;
类型中对于字段的定义称为映射,比如name映射为字符串类型。
③索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。
什么是节点和分片?
一个集群包含至少一个节点,而一个节点就是一个elasticsearch进程。节点内可以有多个索引。
默认的,如果你创建一个索引,那么这个索引将会有5个分片(primary shard ,又称主分片)构成,而每个分片又有一个副本(replica shard ,又称复制分片),这样,就有了10个分片。如下图
上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。 实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。
倒排索引
elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索作为底层。elasticsearch将索引被分为多个分片,每份分片是一个Lucene的索引。所以一个elasticsearch索引是由多个Lucene索引组成的。这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。
如下表,进一步理解倒排索引:
学科(原始数据)
索引列表(倒排索引)
id
学科
语言
id
1
语文
语文
1,2,3
2
语文
数学
3,4
3
语文,数学
4
数学
MySQL,MongoDB,Elasticsearch对比
关系型数据库和非关系型数据库比对
MySQL
MongoDB
Elasticsearch
数据库(database)
数据库
索引(indices)
表(tables)
表(Collection)
types
行(rows)
Documents
Documents
字段(columns)
Field
Field
总结:MySQL为关系型数据库,而MongoDB,Elasticsearch为非关系型数据库,由此看出两者的特点比较明显;
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个文档中又包含多个字段(列)。
物理设计 ---> elasticsearch后台是如何处理这些数据的呢?elasticsearch将每个索引划分为多个分片,每份分片又可以在集群中的不同服务器间迁移。
注意:当然,这里需要补充的是,从elasticsearch的第一个版本开始,每个文档都存储在一个索引中,并分配多个映射类型,映射类型用于表示被索引的文档或者实体的类型,但这也带来了一些问题(详情参见Removal of mapping types ),导致后来在elasticsearch6.0.0版本中一个文档只能包含一个映射类型,而在7.0.0中,映射类型则将被弃用,到了8.0.0中则将完全被删除。
elasticsearch的语法
1.基本语法写法
# 基本语法
# PUT创建一文档(注意大写)t1的文档
PUT t1/doc/1
{
" name " :" 可可 "
}
# 查询t1的文档索引信息
GET t1/doc/1
# 删除索引
DELETE t1/doc/1
PUT t1 /doc/2
{
" name " :" 盼盼 " ,
" age " :20
}
GET t1 # 返回t1所有的创建信息
GET _cat/indices # 查询所有文档的信息
GET t1/_settings # 查询t1的主分片和复制分片情况
GET t1/_mapping # 查看t1的映射关系结构
创建一些数据来操作操作
# 是不是有种来到汉朝的感觉,没事,放松点,呵呵
PUT han/doc/1
{
" name " :" 孝景帝 " ,
" from " :" 汉朝 " ,
" in_time " :16,
" info " :" 无为而治 " ,
" tags " :[" 平定七国之乱 " ," 诛晁错 " ],
" female " :" 窦太后 "
}
PUT han /doc/2
{
" name " :" 孝武帝 " ,
" from " :" 汉朝 " ,
" in_time " :54,
" info " :" 以战养战 " ,
" tags " :[" 罢黜百家,独尊儒术 " ," 推恩令 " ," 平定北方匈奴 " ," 丝绸之路 " ],
" female " :" 卫子夫 "
}
PUT han /doc/3
{
" name " :" 汉高祖 " ,
" from " :" 汉朝 " ,
" in_time " :7,
" info " :" 建立汉朝 " ,
" tags " :[" 鸿门宴 " ," 灭秦,楚国 " ],
" female " :" 吕雉 "
}
PUT han /doc/4
{
" name " :" 卫青 " ,
" from " :" 孝武帝 " ,
" in_time " :2,
" title " :" 关内侯 " ,
" info " :" 初伐匈奴 " ,
" tags " :[" 奇袭龙城 " ]
}
PUT han /doc/5
{
" name " :" 李广 " ,
" in_time " :1,
" from " :" 孝武帝 " ,
" title " :" 飞将军 " ,
" info " :" 射石搏虎 " ,
" tags " :[" 平七国吴楚联军 " ]
}
PUT han /doc/6
{
" name " :" 霍去病 " ,
" from " :" 孝武帝 " ,
" in_time " :3,
" title " :" 骠骑校尉 " ,
" info " :" 英年早逝,封景桓侯 " ,
" tags " :[" 八百铁骑,直插敌后 " ," 平定朔方,打通西域 " ]
}
PUT han /doc/7
{
" name " :" 李广利 " ,
" from " :" 孝武帝 " ,
" title " :" 贰师将军 " ,
" in_time " :4,
" info " :" 二征大宛 " ,
" tags " :[" 征服大宛 " ]
}
创建数据
2.结构化查询(match相关)
"""match按关键字查询"""
# match_all查询全部
GET han/doc/_search
{
" query " : {
" match_all " : {}
}
}
# match指定字段查询,但是这样会查出来所有name字段中跟孝武帝相关的数据,列如孝景帝.
GET han/doc/_search
{
" query " : {
" match " : {
" name " :" 孝武帝 "
}
}
}
# 单独查询某条数据match_phrase,只会查到name是'孝武帝'的数据
GET han/doc/_search
{
" query " : {
" match_phrase " : {
" name " : " 孝武帝 "
}
}
}
# 最左前缀查询,match_phrase_prefix,不记得字段信息了,智能匹配
GET han/doc/_search
{
" query " : {
" match_phrase_prefix " : {
" name " : " 霍 "
}
}
}
# 多字段查询multi_match ,查到字段中含有"帝"的数据
GET han/doc/_search
{
" query " : {
" multi_match " : {
" query " : " 帝 " ,
" fields " : [" name " ," title " ]
}
}
}
# multi_match甚至可以match_phrase,match_phrase_prefix使用,只要指定type即可
GET han /doc/_search
{
" query " : {
" multi_match " : {
" query " : " 青 " ,
" fields " : [" name " ],
" type " : " phrase "
}
}
}
GET han /doc/_search
{
" query " : {
" multi_match " : {
" query " : " 孝 " ,
" fields " : [" name " ],
" type " : " phrase_prefix "
}
}
}
# 排序sort
# 降序
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" sort " :[
{
" in_time " :{
" order " :" desc "
}
}
]
}
# 升序
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" sort " : [
{
" in_time " : {
" order " : " asc "
}
}
]
}
排序语法
# 过滤_source
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" sort " : [
{
" in_time " : {
" order " : " asc "
}
}
],
" _source " : [ " from " , " in_time " ," name " ]
}
过滤 _source
# 分页 from从哪开始,size:展示条数,from,size也可以为-1
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" from " :2,
" size " :2,
" _source " : " in_time "
}
分页
# 高亮 highlight
GET han/doc/_search
{
" query " : {
" match " : {
" from " :" 孝武帝 "
}
},
" highlight " :{
" pre_tags " :" <b style='color:red;'> " ,
" post_tags " :" </b> " ,
" fields " :{
" from " : {}
}
}
}
搜素信息 高亮
# bool查询:must(and)/should(or)/must_not(not)
# and
GET han/doc/_search
{
" query " : {
" bool " : {
" must " : [
{ " match " : {
" in_time " : " 1 "
}
},
{
" match " : {
" from " : " 孝武帝 "
}
}
]
}
}
}
# or
GET han/doc/_search
{
" query " : {
" bool " : {
" should " : [
{ " match " : {
" from " : " 汉朝 "
}
},
{
" match " : {
" in_time " : 54
}
}
]
}
}
}
# not
GET han/doc/_search
{
" query " : {
" bool " : {
" must_not " : [
{ " match " : {
" from " : " 孝武帝 "
}
},
{
" match " : {
" in_time " : 54
}
}
]
}
}
}
bool查询:must,should,must_not
# 查询filter:gte(大于等于) gt(大于)lt(小于)lte(小于等于)
GET han /doc/_search
{
" query " : {
" bool " : {
" must " : [
{
" match " : {
" from " : " 汉朝 "
}
}
],
" filter " : {
" range " : {
" in_time " : {
" gte " : 10
}
}
}
}
}
}
# 小于
GET han/doc/_search
{
" query " : {
" bool " : {
" must " : [
{
" match " : {
" from " : " 汉朝 "
}
}
],
" filter " : {
" range " : {
" in_time " : {
" lt " : 10
}
}
}
}
}
}
filter查询lte,lt,gt,gte
# 聚合查询:avg,sum,max,min
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" aggs " : {
" my_a1 " : {
" avg " : {
" field " : " in_time "
}
}
}
}
聚合查询 aggs
# 分组
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" aggs " : {
" my_group " : {
" range " : {
" field " : " in_time " ,
" ranges " : [
{
" from " : 20,
" to " : 60
}
]
}
}
}
}
# 分组再聚合
GET han/doc/_search
{
" query " : {
" match_all " : {}
},
" aggs " : {
" my_group " : {
" range " : {
" field " : " in_time " ,
" ranges " : [
{
" from " : 10,
" to " : 60
}
]
},
" aggs " : {
" my_sum " : {
" sum " : {
" field " : " in_time "
}
}
}
}
}
}
分组查询,分组再聚合
3.映射
映射用来自定义一个文档及其包含的字段如何存储和索引的过程
例如,我们可以使用映射来定义:
哪些字符串应该被视为全文字段。
哪些字段包含数字、日期或者地理位置。
定义日期的格式。
自定义的规则,用来控制动态添加字段的的映射。
# 映射mappings写法-01 dynamic没写默认为true,可以添加字段
PUT t3
{
" mappings " :{
" doc " :{
" properties " :{
" name " :{
" type " :" text "
},
" age " :{
" type " :" long "
}
}
}
}
}
GET t3 /_mapping
PUT t3 /doc/1
{
" name " :" 窦颖 " ,
" age " :17,
" desc " :" 可爱 "
}
GET t3 /doc/_search
{
" query " : {
" match " : {
" age " : 17
}
}
}
# mappings写法-02 dynamic=false 不让添加字段,但不报错
PUT t4
{
" mappings " :{
" doc " :{
" dynamic " :false,
" properties " :{
" name " :{
" type " :" text "
},
" age " :{
" type " :" long "
}
}
}
}
}
GET t4 /_mapping
PUT t4 /doc/1
{
" name " :" 颖 " ,
" age " :19,
" desc " :" 可ke爱 "
}
# mappings写法-03 dynamic=strict 严格模式,只要添加就报错
PUT t5
{
" mappings " : {
" doc " :{
" dynamic " :" strict " ,
" properties " :{
" name " :{
" type " :" text "
}
}
}
}
}
GET t5 /_mapping
PUT t5 /doc/1
{
" name " :" coco "
}
# 缺省/严格的时候不能添加字段
PUT t5/doc/2
{
" name " :" cici " ,
" age " :9
}
mappings自定义形式和dynamic的三种形态
# index属性,类似于模糊查询配置,有两种状态,当true,我们的自定义字段可以实现模糊查询,false则会报错
PUT t6
{
" mappings " : {
" doc " :{
" properties " :{
" title " :{
" type " :" text " ,
" index " :true
},
" content " :{
" type " :" text " ,
" index " :false
}
}
}
}
}
PUT t6 /doc/1
{
" title " :" es长路漫漫 " ,
" content " :" py唯你作伴 "
}
# 报错
GET t6/doc/_search
{
" query " : {
" match " : {
" title " :" 漫漫 "
}
}
}
GET t6 /doc/_search
{
" query " : {
" match " : {
" content " :" py "
}
}
}
mappings写法02 index
# copy_to,该属性允许我们将多个字段的值复制到组字段中,然后将组字段作为单个字段进行查询。
# copy_to写法-01
PUT t7
{
" mappings " : {
" doc " :{
" properties " :{
" title " :{
" type " :" text " ,
" copy_to " :" full_name "
},
" content " :{
" type " :" text " ,
" copy_to " :" full_name "
},
" full_name " :{
" type " :" text "
}
}
}
}
}
PUT t7 /doc/1
{
" title " :" aa " ,
" content " :" bb "
}
GET t7 /doc/_search
{
" query " : {
" match " : {
" full_name " : " bb "
}
}
}
# copy_to写法-02
PUT t8
{
" mappings " : {
" doc " :{
" properties " :{
" title " :{
" type " :" text " ,
" copy_to " :[" full_name1 " ," full_name2 " ]
},
" content " :{
" type " :" text " ,
" copy_to " :" full_name "
},
" full_name1 " :{
" type " :" text "
},
" full_name2 " :{
" type " :" text "
}
}
}
}
}
mappings写法03 copy_to
# 对象属性,mappings中嵌套结构写法,查询方式:info.addr
PUT t9
{
" mappings " : {
" doc " :{
" dynamic " :false,
" properties " :{
" name " :{
" type " :" text "
},
" age " :{
" type " :" long "
},
" info " :{
" properties " :{
" addr " :{
" type " :" text "
},
" tel " :{
" type " :" text "
}
}
}
}
}
}
}
PUT t9 /doc/1
{
" name " :" vov " ,
" age " :19,
" info " :{
" addr " :" earth " ,
" tel " :" no "
}
}
GET t9 /doc/_search
{
" query " : {
" match " : {
" info.addr " : " earth "
}
}
}
mappings写法04 对象属性
# settings设置,设置主,复制分片.
# number_of_shards主分片数量,默认5,number_of_replicas复制分片,默认1个
PUT w1
{
" mappings " : {
" doc " :{
" properties " :{
" title " :{
" type " :" text "
}
}
}
},
" settings " : {
" number_of_shards " : 3,
" number_of_replicas " : 2
}
}
GET w1
mappings写法05 settings
...待续
CrazyShenldon
posted @
2019-04-25 21:21
FindSoul
阅读(
203 )
评论()
收藏
举报
var RENDERER = {
POINT_INTERVAL : 5,
FISH_COUNT : 3,
MAX_INTERVAL_COUNT : 50,
INIT_HEIGHT_RATE : 0.5,
THRESHOLD : 50,
init : function(){
this.setParameters();
this.reconstructMethods();
this.setup();
this.bindEvent();
this.render();
},
setParameters : function(){
this.$window = $(window);
this.$container = $('#jsi-flying-fish-container');
this.$canvas = $(' ');
this.context = this.$canvas.appendTo(this.$container).get(0).getContext('2d');
this.points = [];
this.fishes = [];
this.watchIds = [];
},
createSurfacePoints : function(){
var count = Math.round(this.width / this.POINT_INTERVAL);
this.pointInterval = this.width / (count - 1);
this.points.push(new SURFACE_POINT(this, 0));
for(var i = 1; i < count; i++){
var point = new SURFACE_POINT(this, i * this.pointInterval),
previous = this.points[i - 1];
point.setPreviousPoint(previous);
previous.setNextPoint(point);
this.points.push(point);
}
},
reconstructMethods : function(){
this.watchWindowSize = this.watchWindowSize.bind(this);
this.jdugeToStopResize = this.jdugeToStopResize.bind(this);
this.startEpicenter = this.startEpicenter.bind(this);
this.moveEpicenter = this.moveEpicenter.bind(this);
this.reverseVertical = this.reverseVertical.bind(this);
this.render = this.render.bind(this);
},
setup : function(){
this.points.length = 0;
this.fishes.length = 0;
this.watchIds.length = 0;
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.width = this.$container.width();
this.height = this.$container.height();
this.fishCount = this.FISH_COUNT * this.width / 500 * this.height / 500;
this.$canvas.attr({width : this.width, height : this.height});
this.reverse = false;
this.fishes.push(new FISH(this));
this.createSurfacePoints();
},
watchWindowSize : function(){
this.clearTimer();
this.tmpWidth = this.$window.width();
this.tmpHeight = this.$window.height();
this.watchIds.push(setTimeout(this.jdugeToStopResize, this.WATCH_INTERVAL));
},
clearTimer : function(){
while(this.watchIds.length > 0){
clearTimeout(this.watchIds.pop());
}
},
jdugeToStopResize : function(){
var width = this.$window.width(),
height = this.$window.height(),
stopped = (width == this.tmpWidth && height == this.tmpHeight);
this.tmpWidth = width;
this.tmpHeight = height;
if(stopped){
this.setup();
}
},
bindEvent : function(){
this.$window.on('resize', this.watchWindowSize);
this.$container.on('mouseenter', this.startEpicenter);
this.$container.on('mousemove', this.moveEpicenter);
this.$container.on('click', this.reverseVertical);
},
getAxis : function(event){
var offset = this.$container.offset();
return {
x : event.clientX - offset.left + this.$window.scrollLeft(),
y : event.clientY - offset.top + this.$window.scrollTop()
};
},
startEpicenter : function(event){
this.axis = this.getAxis(event);
},
moveEpicenter : function(event){
var axis = this.getAxis(event);
if(!this.axis){
this.axis = axis;
}
this.generateEpicenter(axis.x, axis.y, axis.y - this.axis.y);
this.axis = axis;
},
generateEpicenter : function(x, y, velocity){
if(y < this.height / 2 - this.THRESHOLD || y > this.height / 2 + this.THRESHOLD){
return;
}
var index = Math.round(x / this.pointInterval);
if(index < 0 || index >= this.points.length){
return;
}
this.points[index].interfere(y, velocity);
},
reverseVertical : function(){
this.reverse = !this.reverse;
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].reverseVertical();
}
},
controlStatus : function(){
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateSelf();
}
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateNeighbors();
}
if(this.fishes.length < this.fishCount){
if(--this.intervalCount == 0){
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.fishes.push(new FISH(this));
}
}
},
render : function(){
requestAnimationFrame(this.render);
this.controlStatus();
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = 'hsl(0, 0%, 95%)';
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].render(this.context);
}
this.context.save();
this.context.globalCompositeOperation = 'xor';
this.context.beginPath();
this.context.moveTo(0, this.reverse ? 0 : this.height);
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].render(this.context);
}
this.context.lineTo(this.width, this.reverse ? 0 : this.height);
this.context.closePath();
this.context.fill();
this.context.restore();
}
};
var SURFACE_POINT = function(renderer, x){
this.renderer = renderer;
this.x = x;
this.init();
};
SURFACE_POINT.prototype = {
SPRING_CONSTANT : 0.03,
SPRING_FRICTION : 0.9,
WAVE_SPREAD : 0.3,
ACCELARATION_RATE : 0.01,
init : function(){
this.initHeight = this.renderer.height * this.renderer.INIT_HEIGHT_RATE;
this.height = this.initHeight;
this.fy = 0;
this.force = {previous : 0, next : 0};
},
setPreviousPoint : function(previous){
this.previous = previous;
},
setNextPoint : function(next){
this.next = next;
},
interfere : function(y, velocity){
this.fy = this.renderer.height * this.ACCELARATION_RATE * ((this.renderer.height - this.height - y) >= 0 ? -1 : 1) * Math.abs(velocity);
},
updateSelf : function(){
this.fy += this.SPRING_CONSTANT * (this.initHeight - this.height);
this.fy *= this.SPRING_FRICTION;
this.height += this.fy;
},
updateNeighbors : function(){
if(this.previous){
this.force.previous = this.WAVE_SPREAD * (this.height - this.previous.height);
}
if(this.next){
this.force.next = this.WAVE_SPREAD * (this.height - this.next.height);
}
},
render : function(context){
if(this.previous){
this.previous.height += this.force.previous;
this.previous.fy += this.force.previous;
}
if(this.next){
this.next.height += this.force.next;
this.next.fy += this.force.next;
}
context.lineTo(this.x, this.renderer.height - this.height);
}
};
var FISH = function(renderer){
this.renderer = renderer;
this.init();
};
FISH.prototype = {
GRAVITY : 0.4,
init : function(){
this.direction = Math.random() < 0.5;
this.x = this.direction ? (this.renderer.width + this.renderer.THRESHOLD) : -this.renderer.THRESHOLD;
this.previousY = this.y;
this.vx = this.getRandomValue(4, 10) * (this.direction ? -1 : 1);
if(this.renderer.reverse){
this.y = this.getRandomValue(this.renderer.height * 1 / 10, this.renderer.height * 4 / 10);
this.vy = this.getRandomValue(2, 5);
this.ay = this.getRandomValue(0.05, 0.2);
}else{
this.y = this.getRandomValue(this.renderer.height * 6 / 10, this.renderer.height * 9 / 10);
this.vy = this.getRandomValue(-5, -2);
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
this.theta = 0;
this.phi = 0;
},
getRandomValue : function(min, max){
return min + (max - min) * Math.random();
},
reverseVertical : function(){
this.isOut = !this.isOut;
this.ay *= -1;
},
controlStatus : function(context){
this.previousY = this.y;
this.x += this.vx;
this.y += this.vy;
this.vy += this.ay;
if(this.renderer.reverse){
if(this.y > this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy -= this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(0.05, 0.2);
}
this.isOut = false;
}
}else{
if(this.y < this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy += this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
}
}
if(!this.isOut){
this.theta += Math.PI / 20;
this.theta %= Math.PI * 2;
this.phi += Math.PI / 30;
this.phi %= Math.PI * 2;
}
this.renderer.generateEpicenter(this.x + (this.direction ? -1 : 1) * this.renderer.THRESHOLD, this.y, this.y - this.previousY);
if(this.vx > 0 && this.x > this.renderer.width + this.renderer.THRESHOLD || this.vx < 0 && this.x < -this.renderer.THRESHOLD){
this.init();
}
},
render : function(context){
context.save();
context.translate(this.x, this.y);
context.rotate(Math.PI + Math.atan2(this.vy, this.vx));
context.scale(1, this.direction ? 1 : -1);
context.beginPath();
context.moveTo(-30, 0);
context.bezierCurveTo(-20, 15, 15, 10, 40, 0);
context.bezierCurveTo(15, -10, -20, -15, -30, 0);
context.fill();
context.save();
context.translate(40, 0);
context.scale(0.9 + 0.2 * Math.sin(this.theta), 1);
context.beginPath();
context.moveTo(0, 0);
context.quadraticCurveTo(5, 10, 20, 8);
context.quadraticCurveTo(12, 5, 10, 0);
context.quadraticCurveTo(12, -5, 20, -8);
context.quadraticCurveTo(5, -10, 0, 0);
context.fill();
context.restore();
context.save();
context.translate(-3, 0);
context.rotate((Math.PI / 3 + Math.PI / 10 * Math.sin(this.phi)) * (this.renderer.reverse ? -1 : 1));
context.beginPath();
if(this.renderer.reverse){
context.moveTo(5, 0);
context.bezierCurveTo(10, 10, 10, 30, 0, 40);
context.bezierCurveTo(-12, 25, -8, 10, 0, 0);
}else{
context.moveTo(-5, 0);
context.bezierCurveTo(-10, -10, -10, -30, 0, -40);
context.bezierCurveTo(12, -25, 8, -10, 0, 0);
}
context.closePath();
context.fill();
context.restore();
context.restore();
this.controlStatus(context);
}
};
$(function(){
RENDERER.init();
});