canvas草木树木生长动画效果代码
代码语言:html
所属分类:动画
代码描述:canvas草木树木生长动画效果代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body { background: #111111;}#canvas_container_div { position: absolute; top: 30%; left: 50%; transform: translate(-50%,-50%);}#canvas { width: 100%; height: 100%;/* border: solid 3px #000000; */ background: #111111;} </style> </head> <body> <div id="canvas_container_div"><canvas id="canvas" width="1000" height="1000"></canvas> </div> <script> /////////////////////////////////////////////////////// //////// Plant Life Game Growth Prototype //// /////////////////////////////////////////////////////// ///////////////////// VERLET.JS //////////////////////////// ////---INITIATION---//// ///canvas (*canvas width & height must be equal to retain aspect ratio) var canvasContainerDiv = document.getElementById("canvas_container_div"); var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var canvRatio = 1;//0.8; // canvas ratio, as canvas size to lowest of window width or height ///trackers var points = [], pointCount = 0; var spans = [], spanCount = 0; var skins = [], skinCount = 0; var worldTime = 0; // time as frame count ///settings var gravity = 0.01; // (rate of y-velocity increase per frame per point mass of 1) var rigidity = 10; // global span rigidity (as iterations of position accuracy refinement) var friction = 0.999; // (proportion of previous velocity after frame refresh) var bounceLoss = 0.9; // (proportion of previous velocity after bouncing) var skidLoss = 0.8; // (proportion of previous velocity if touching the ground) var viewPoints = false; // (point visibility) var viewSpans = false; // (span visibility) var viewScaffolding = false; // (scaffolding visibility) var viewSkins = true; // (skin visibility) var breeze = 0.4; // breeziness level (applied as brief left & right gusts) ////---OBJECTS---//// ///point constructor function Point(current_x, current_y, materiality="material") { // materiality can be "material" or "immaterial" this.cx = current_x; this.cy = current_y; this.px = this.cx; // previous x value this.py = this.cy; // previous y value this.mass = 1; // (as ratio of gravity) this.materiality = materiality; this.fixed = false; this.id = pointCount; pointCount += 1; } ///span constructor function Span(point_1, point_2, visibility="visible") { // visibility can be "visible" or "hidden" this.p1 = point_1; this.p2 = point_2; this.l = distance(this.p1,this.p2); // length this.strength = 1; // (as ratio of rigidity) this.visibility = visibility; this.id = spanCount; spanCount += 1; } ///skins constructor function Skin(points_array,color) { this.points = points_array; // an array of points for skin outline path this.color = color; this.id = skinCount; skinCount += 1; } ////---FUNCTIONS---//// ///scales canvas to window function scaleToWindow() { if (window.innerWidth > window.innerHeight) { canvasContainerDiv.style.height = window.innerHeight*canvRatio+"px"; canvasContainerDiv.style.width = canvasContainerDiv.style.height; } else { canvasContainerDiv.style.width = window.innerWidth*canvRatio+"px"; canvasContainerDiv.style.height = canvasContainerDiv.style.width; } } ///converts percentage to canvas x value function xValFromPct(percent) { return percent * canvas.width / 100; } ///converts percentage to canvas y value function yValFromPct(percent) { return percent * canvas.height / 100; } ///converts canvas x value to percentage of canvas width function pctFromXVal(xValue) { return xValue * 100 / canvas.width; } ///converts canvas y value to percentage of canvas height function pctFromYVal(yValue) { return yValue * 100 / canvas.height; } ///gets a point by id number function getPt(id) { for (var i=0; i<points.length; i++) { if (points[i].id == id) { return points[i]; } } } ///gets distance between two points (pythogorian theorum) function distance(point_1, point_2) { var x_difference = point_2.cx - point_1.cx; var y_difference = point_2.cy - point_1.cy; return Math.sqrt( x_difference*x_difference + y_difference*y_difference); } ///gets a span's mid point (returns object: { x: <value>, y: <value> } ) function smp(span) { var mx = ( span.p1.cx + span.p2.cx ) / 2; // mid x value var my = ( span.p1.cy + span.p2.cy ) / 2; // mid y value return { x: mx, y: my}; } ///removes a span by id function removeSpan(id) { for( var i = 0; i < spans.length-1; i++){ if ( spans[i].id === id) { spans.splice(i, 1); } } } ///creates a point object instance function addPt(xPercent,yPercent,materiality="material") { points.push( new Point( xValFromPct(xPercent), yValFromPct(yPercent), materiality ) ); return points[points.length-1]; } ///creates a span object instance function addSp(p1,p2,visibility="visible") { spans.push( new Span( getPt(p1), getPt(p2), visibility ) ); return spans[spans.length-1]; } ///creates a skin object instance function addSk(id_path_array, color) { var points_array = []; for ( var i=0; i<id_path_array.length; i++) { points_array.push(points[id_path_array[i]]); } skins.push( new Skin(points_array,color) ); return skins[skins.length-1]; } ///updates point positions based on verlet velocity (i.e., current coord minus previous coord) function updatePoints() { for(var i=0; i<points.length; i++) { var p = points[i]; // point object if (!p.fixed) { var xv = (p.cx - p.px) * friction; // x velocity var yv = (p.cy - p.py) * friction; // y velocity if (p.py >= canvas.height-1 && p.py <= canvas.height) { xv *= skidLoss; } p.px = p.cx; // updates previous x as current x p.py = p.cy; // updates previous y as current y p.cx += xv; // updates current x with new velocity p.cy += yv; // updates current y with new velocity p.cy += gravity * p.mass; // add gravity to y if (worldTime % Tl.rib( 100, 200 ) === 0) { p.cx += Tl.rfb( -breeze, breeze ); } // apply breeze to x } } } ///applies constrains function applyConstraints( currentIteration ) { for (var i=0; i<points.length; i++) { var p = points[i]; //wall constraints (inverts velocity if point moves beyond a canvas edge) if (p.materiality === "material") { if (p.cx > canvas.width) { p.cx = canvas.width; p.px = p.cx + (p.cx - p.px) * bounceLoss; } if (p.cx < 0) { p.cx = 0; p.px = p.cx + (p.cx - p.px) * bounceLoss; } if (p.cy > canvas.height) { p.cy = canvas.height; p.py = p.cy + (p.cy - p.py) * bounceLoss; } if (p.cy < 0) { p.cy = 0; p.py = p.cy + (p.cy - p.py) * bounceLoss; } } } } ///updates span positions and adjusts associated points function updateSpans( currentIteration ) { for (var i=0; i<spans.length; i++) { var thisSpanIterations = Math.round( rigidity * spans[i].strength ); if ( currentIteration+1 <= thisSpanIterations ) { var s = spans[i]; var dx = s.p2.cx - s.p1.cx; // distance between x values var dy = s.p2.cy - s.p1.cy; // distance between y values var d = Math.sqrt( dx*dx + dy*dy); // distance between the points var r = s.l / d; // ratio (span length over distance between points) var mx = s.p1.cx + dx / 2; // midpoint between x values var my = s.p1.cy + dy / 2; // midpoint between y values var ox = dx / 2 * r; // offset of each x value (compared to span length) var oy = dy / 2 * r; // offset of each y value (compared to span length) if (!s.p1.fixed) { s.p1.cx = mx - ox; // updates span's first point x value s.p1.cy = my - oy; // updates span's first point y value } if (!s.p2.fixed) { s.p2.cx = mx + ox; // updates span's second point x value s.p2.cy = my + oy; // updates span's second point y value } } } } ///refines points for position accuracy & shape rigidity by updating spans and applying constraints iteratively function refinePositions() { var requiredIterations = rigidity; for (var i=0; i<spans.length; i++) { var thisSpanIterations = Math.round( rigidity * spans[i].strength ); if ( thisSpanIterations > requiredIterations ) { requiredIterations = thisSpanIterations; } } for (var j=0; j<requiredIterations; j++) { updateSpans(j); applyConstraints(i); } } ///displays points function renderPoints() { for (var i=0; i<points.length; i++) { var p = points[i]; ctx.beginPath(); ctx.fillStyle = "blue"; ctx.arc( p.cx, p.cy, 3, 0 , Math.PI*2 ); ctx.fill(); } } ///displays spans function renderSpans() { for (var i=0; i<spans.length; i++) { var s = spans[i]; if (s.visibility == "visible") { ctx.beginPath(); ctx.lineWidth = 1; ctx.strokeStyle = "blue"; ctx.moveTo(s.p1.cx, s.p1.cy); ctx.lineTo(s.p2.cx, s.p2.cy); ctx.stroke(); } } } ///displays scaffolding & binding spans (i.e., "hidden" spans) function renderScaffolding() { ctx.beginPath(); for (var i=0; i<spans.length; i++) { var s = spans[i]; if (s.visibility === "hidden") { ctx.strokeStyle="pink"; ctx.moveTo(s.p1.cx, s.p1.cy); ctx.lineTo(s.p2.cx, s.p2.cy); } } ctx.stroke(); } ///displays skins function renderSkins() { for(var i=0; i<skins.length; i++) { var s = skins[i]; ctx.beginPath(); ctx.strokeStyle = s.color; ctx.lineWidth = 0; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.fillStyle = s.color; ctx.moveTo(s.points[0].cx, s.points[0].cy); for(var j=1; j<s.points.length; j++) { ctx.lineTo(s.points[j].cx, s.points[j].cy); } ctx.lineTo(s.points[0].cx, s.points[0].cy); ctx.stroke(); ctx.fill(); } } ///clears canvas frame function clearCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); } ///renders all visible components function renderImages() { //if ( viewSkins ) { renderSkins(); } // disabled here so plants can be rendered sequentially in plants.js if ( viewSpans ) { renderSpans(); } if ( viewPoints ) { renderPoints(); } if ( viewScaffolding ) { renderScaffolding(); } } ////---EVENTS---//// ///scaling window.addEventListener('resize', scaleToWindow); ////---RUN---//// function runVerlet() { scaleToWindow(); updatePoints(); refinePositions(); clearCanvas(); renderImages(); worldTime++; } ////////////////////// TOOLS.JS //////////////////////////// const Tl = { //random integer between two numbers (min/max inclusive) rib: function( min, max ) { return Math.floor( Math.random() * ( Math.floor(max) - Math.ceil(min) + 1 ) ) + Math.ceil(min); }, //random float between two numbers rfb: function( min, max ) { return Math.random() * ( max - min ) + min; }, //converts radians to degrees radToDeg: function( radian ) { return radian * 180 / Math.PI; }, //converts degrees to radians degToRad: function( degree ) { return degree / 180 * Math.PI; }, //pauses program pause: function( milliseconds ) { var then = Date.now(); var now; do { now = Date.now() } while ( now - then < milliseconds ); } }; ///////////////////////////////////////////////////////////// ///////////////////// PLANT LIFE //////////////////////////// ///////////////////////////////////////////////////////////// ////---INITIATION---//// ///trackers var plants = [], plantCount = 0; var sunRays = [], sunRayCount = 0; var shadows = [], shadowCount = 0; ///settings var worldSpeed = 1;//3; // (as frames per iteration; higher is slower) (does not affect physics iterations) var restrictGrowthByEnergy = false; // restricts plant growth by energy level (if false plants grow freely) var viewShadows = false; // (shadow visibility) var phr = 2; // photosynthesis rate ( rate plants store energy from sunlight ) var geer = 0.5; // growth energy expenditure rate (rate energy is expended for growth) var leer = 0.03; // living energy expenditure rate (rate energy is expended for living, per segment) ////---OBJECTS---//// ///plant constructor function Plant( xLocation ) { this.id = plantCount; this.segments = []; this.segmentCount = 0; this.xLocation = xLocation; this.energy = 5000; // seed energy (starting energy level at germination) this.isAlive = true; //settings this.forwardGrowthRate = gravity * Tl.rfb(35,50);//Tl.rfb(18,22); // (rate of cross spans increase per frame) this.outwardGrowthRate = this.forwardGrowthRate * Tl.rfb(0.18,0.22); // (rate forward span widens per frame) this.maxSegmentWidth = Tl.rfb(11,13); // maximum segment width (in pixels) this.maxTotalSegments = Tl.rib(10,20); // maximum total number of segments this.firstLeafSegment = Tl.rib(2,4); // (segment on which first leaf set grows) this.leafFrequency = Tl.rib(2,3); // (number of segments until next leaf set) this.maxLeaflength = this.maxSegmentWidth * Tl.rfb(4,7); // maximum leaf length at maturity this.leafGrowthRate = this.forwardGrowthRate * Tl.rfb(1.4,1.6); // leaf growth rate //base segment this.ptB1 = addPt( this.xLocation - 0.1, 100 ); // base point 1 this.ptB2 = addPt( this.xLocation + 0.1, 100 ); // base point 2 this.ptB1.fixed = this.ptB2.fixed = true; // fixes base points to ground this.spB = addSp( this.ptB1.id, this.ptB2.id ); // adds base span createSegment( this, null, this.ptB1, this.ptB2 ); // creates the base segment (with "null" parent) } ///segment constructor function Segment( plant, parentSegment, basePoint1, basePoint2 ) { this.plantId = plant.id; this.id = plant.segmentCount; this.childSegment = null; this.hasChildSegment = false; this.parentSegment = parentSegment; this.isBaseSegment = false; if (this.parentSegment === null) { this.isBaseSegment = true; } this.hasLeaves = false; this.hasLeafScaffolding = false; //settings this.forwardGrowthRateVariation = Tl.rfb(0.95,1.05);//(0.95,1.05); // forward growth rate variation this.mass = 1; // mass of the segment stalk portion ( divided between the two extension points) this.strength = 1.5; // as multiple of global rigidity (higher values effect performance) //base points this.ptB1 = basePoint1; // base point 1 this.ptB2 = basePoint2; // base point 2 //extension points var originX = ( this.ptB1.cx + this.ptB2.cx ) / 2; // center of base points x values var originY = ( this.ptB1.cy + this.ptB2.cy ) / 2; // center of base points y values this.ptE1 = addPt( pctFromXVal( originX ) - 0.1, .........完整代码请登录后点击上方下载按钮下载查看
网友评论0