<div id="container"></div>
html { overflow: hidden; }

body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  color: #030303;
  background: #030303;
}
var OutlineShader = {

  uniforms: {
    offset: { type: 'f', value: 0.3 },
    color: { type: 'v3', value: new THREE.Color('#AE00FF') },
    alpha: { type: 'f', value: 1.0 }
  },

  vertexShader: [

    "uniform float offset;",

    "void main() {",
    "  vec4 pos = modelViewMatrix * vec4( position + normal * offset, 1.0 );",
    "  gl_Position = projectionMatrix * pos;",
    "}"

  ].join('\n'),

  fragmentShader: [

    "uniform vec3 color;",
    "uniform float alpha;",

    "void main() {",
    "  gl_FragColor = vec4( color, alpha );",
    "}"

  ].join('\n')

};

var container = document.getElementById( 'container' );

var renderer = new THREE.WebGLRenderer( {  alpha: true } );
renderer.setClearColor(0xffffff, 0);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.domElement.style.cursor = 'pointer';

var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, .1, 100000 );
camera.position.set( 0, -6, 3 );

var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.target.y = 1;
controls.enableDamping = true;
controls.enabled = false;

var scene = new THREE.Scene();
scene.background = null
scene.fog = new THREE.Fog( scene.background, 100, 200 );

// lights
var aLight = new THREE.AmbientLight( 0xAE00FF );
scene.add( aLight );

var dLight1 = new THREE.DirectionalLight( 0xffffff, 0.4 );
dLight1.position.set( 0.7, 1, 1 );
scene.add( dLight1 );

// var dlh1 = new THREE.DirectionalLightHelper( dLight1, 0.5 );
// scene.add( dlh1 );

// var gh = new THREE.GridHelper( 2, 10, 0x000000, 0x808080 );
// scene.add( gh );

// -------------------------------------------

var rocketGroup = new THREE.Group();
scene.add( rocketGroup );

var rocket = new THREE.Group();
rocket.position.y = -1.5; // vertically center
rocketGroup.add( rocket );

// -------------------------------------------

// body

var points = [];

points.push( new THREE.Vector2( 0, 0 ) ); // bottom

for ( var i = 0; i < 11; i ++ ) {
  var point = new THREE.Vector2(
    Math.cos( i * 0.227 - 0.75 ) * 8,
    i * 4.0
  );

  points.push( point );
}

points.push( new THREE.Vector2( 0, 40 ) ); // tip

var rocketGeo = new THREE.LatheGeometry( points, 32 );

var rocketMat = new THREE.MeshToonMaterial({
  color: 0xAE00FF,
  shininess: 1
});

var rocketOutlineMat = new THREE.ShaderMaterial({
  uniforms: THREE.UniformsUtils.clone( OutlineShader.uniforms ),
  vertexShader: OutlineShader.vertexShader,
  fragmentShader: OutlineShader.fragmentShader,
  side: THREE.BackSide,
});

var rocketObj = THREE.SceneUtils.createMultiMaterialObject(
  rocketGeo, [ rocketMat, rocketOutlineMat ]
);
rocketObj.scale.setScalar( 0.1 );
rocket.add( rocketObj );

// var wireframe = new THREE.WireframeGeometry( rocketGeo );
// var line = new THREE.LineSegments( wireframe );
// line.material.color.setHex( 0x000000 );
// rocketObj.add( line );

// -------------------------------------------

// window

var portalGeo = new THREE.CylinderBufferGeometry( 0.26, 0.26, 1.6, 32 );
var portalMat = new THREE.MeshToonMaterial({ color: 0xAE00FF });
var portalOutlineMat = rocketOutlineMat.clone();
portalOutlineMat.uniforms.offset.value = 0.03;
var portal = THREE.SceneUtils.createMultiMaterialObject(
  portalGeo, [ portalMat, portalOutlineMat ]
);
portal.position.y = 2;
portal.rotation.x = Math.PI / 2;
rocket.add( portal );

// ------------

var circle = new THREE.Shape();
circle.absarc( 0, 0, 3.5, 0, Math.PI * 2 );

var hole = new THREE.Path();
hole.absarc( 0, 0, 3, 0, Math.PI * 2 );
circle.holes.push( hole );

var tubeExtrudeSettings = {
  amount: 17,
  steps: 1,
  bevelEnabled: false
};
var tubeGeo = new THREE.ExtrudeGeometry( circle, tubeExtrudeSettings );
tubeGeo.computeVertexNormals();
tubeGeo.center();
var tubeMat = new THREE.MeshToonMaterial({
  color: 0xAE00FF,
  shininess: 1
});
var tubeOutlineMat = rocketOutlineMat.clone();
tubeOutlineMat.uniforms.offset.value = 0.2;
var tube = THREE.SceneUtils.createMultiMaterialObject(
  tubeGeo, [ tubeMat, tubeOutlineMat ]
);
tube.position.y = 2;
tube.scale.setScalar( 0.1 );
rocket.add( tube );

// var wireframe = new THREE.WireframeGeometry( tubeGeo );
// var line = new THREE.LineSegments( wireframe );
// line.material.color.setHex( 0x000000 );
// tube.add( line );

// -------------------------------------------

// wing

var shape = new THREE.Shape();

shape.moveTo( 3, 0 );
shape.quadraticCurveTo( 25, -8, 15, -37 );
shape.quadraticCurveTo( 13, -21, 0, -20 );
shape.lineTo( 3, 0 );

var extrudeSettings = {
  steps: 1,
  amount: 4,
  bevelEnabled: true,
  bevelThickness: 2,
  bevelSize: 2,
  bevelSegments: 5
};

var wingGroup = new THREE.Group();
rocket.add( wingGroup );

var wingGeo = new THREE.ExtrudeGeometry( shape, extrudeSettings );
wingGeo.computeVertexNormals();
var wingMat = new THREE.MeshToonMaterial({
  color: 0xAE00FF,
  shininess: 1,
});
var wingOutlineMat = rocketOutlineMat.clone();
wingOutlineMat.uniforms.offset.value = 1;
var wing = THREE.SceneUtils.createMultiMaterialObject(
  wingGeo, [ wingMat, wingOutlineMat ]
);
wing.scale.setScalar( 0.03 );
wing.position.set( .6, 0.9, 0 );
wingGroup.add( wing );

// var wireframe = new THREE.WireframeGeometry( wingGeo );
// var line = new THREE.LineSegments( wireframe );
// line.material.color.setHex( 0x000000 );
// wing.add( line );

var wing2 = wingGroup.clone();
wing2.rotation.y = Math.PI;
rocket.add( wing2 );

var wing3 = wingGroup.clone();
wing3.rotation.y = Math.PI / 2;
rocket.add( wing3 );

var wing4 = wingGroup.clone();
wing4.rotation.y = - Math.PI / 2;
rocket.add( wing4 );

// -------------------------------------------


// fire

var firePoints = [];

for ( var i = 0; i <= 10; i ++ ) {
  var point = new THREE.Vector2(
    Math.sin( i * 0.18 ) * 8,
    (-10 + i) * 2.5
  );

  firePoints.push( point );
}

var fireGeo = new THREE.LatheGeometry( firePoints, 32 );

var fireMat = new THREE.ShaderMaterial({
  uniforms: {
    color1: { value: new THREE.Color('yellow') },
    color2: { value: new THREE.Color(0xAE00FF) } // orange
  },
  vertexShader: `
    varying vec2 vUv;

    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 color1;
    uniform vec3 color2;

    varying vec2 vUv;

    void main() {
      gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
    }
  `,
});

var fire = new THREE.Mesh(fireGeo, fireMat);
fire.scale.setScalar( 0.06 );
rocket.add( fire );

var fireLight = new THREE.PointLight( 0xAE00FF, 1, 9 );
fireLight.position.set( 0, -1, 0 );
rocket.add( fireLight );

// var fireLightHelper = new THREE.PointLightHelper( fireLight, 0.5 );
// scene.add( fireLightHelper );

var fireUpdate = function() {
  fire.scale.y = THREE.Math.randFloat(0.04, 0.08);
}

// -------------------------------------------

// fire and stars particles

// https://aerotwist.com/tutorials/creating-particles-with-three-js/
// https://aerotwist.com/static/tutorials/creating-particles-with-three-js/demo/
// downloads/paul-lewis-aerotwist/particles-r88

var Particles = function( options ) {

  var color = this.color = options.color || 0xAE00FF;
  var size = this.size = options.size || 0.4;

  var pointCount = this.pointCount = options.pointCount || 40; // 1800
  var rangeV = this.rangeV = options.rangeV || 2; // 600
  var rangeH = this.rangeH = options.rangeH || 1;

  var speed = this.speed = this.speedTarget = options.speed || 0.0005;

  THREE.Group.call( this );

  // circle texture

  var canvas = document.createElement('canvas');
  canvas.width = canvas.height = 128;
  var ctx = canvas.getContext( '2d' );

  var centerX = canvas.width / 2;
  var centerY = canvas.height / 2;
  var radius = canvas.width / 3;

  ctx.beginPath();
  ctx.arc( centerX, centerY, radius, 0, 2 * Math.PI, false );
  ctx.fillStyle = '#fff';
  ctx.fill();

  var texture = new THREE.Texture( canvas );
  texture.premultiplyAlpha = true;
  texture.needsUpdate = true;

  //

  var pointsGeo = new THREE.Geometry();
  var pointsMat = new THREE.PointsMaterial({
    color: color,
    size: size,
    map: texture,
    transparent: true,
    depthWrite: false
  });

  for (var p = 0; p < pointCount; p++) {

    var point = new THREE.Vector3(
      THREE.Math.randFloatSpread( rangeH ),
      THREE.Math.randFloatSpread( rangeV ),
      THREE.Math.randFloatSpread( rangeH )
    );

    point.velocity = new THREE.Vector3( 0, -Math.random() * speed * 100, 0);

    pointsGeo.vertices.push( point );
  }

  var points = this.points = new THREE.Points( pointsGeo, pointsMat );
  points.position.y = - rangeV / 2;
  points.sortParticles = true;

  this.add( points );

}

Particles.prototype = Object.create( THREE.Group.prototype );
Particles.prototype.constructor = Particles;

Particles.prototype.update = function(){
  // this.points.rotation.y -= 0.01; // 0.01

  var pCount = this.pointCount;
  while ( pCount-- ) {

    var point = this.points.geometry.vertices[pCount];

    // check if we need to reset
    if ( point.y < - this.rangeV / 2 ) {
      point.y = this.rangeV / 2;
      point.velocity.y = 0;
    }

    // update the velocity
    point.velocity.y -= Math.random() * this.speed; // .1

    // and the position
    point.add( point.velocity );
  }

  this.points.geometry.verticesNeedUpdate = true;
}

Particles.prototype.updateConstant = function(){
  var pCount = this.pointCount;
  while ( pCount-- ) {

    var point = this.points.geometry.vertices[pCount];

    // check if we need to reset
    if ( point.y < - this.rangeV / 2 ) {
      point.y = this.rangeV / 2;
    }

    point.y -= this.speed;
  }

  this.points.geometry.verticesNeedUpdate = true;
}

// -------------------------------------------

// stars

var stars = new Particles({
  color: 0xffffff,
  size: 0.6,
  rangeH: 20,
  rangeV: 20,
  pointCount: 400,
  size: 0.2,
  speed: 0.1
});

stars.points.position.y = 0;
scene.add( stars );

// -------------------------------------------

// input

//

var plane = new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), 0 );
// var helper = new THREE.PlaneHelper( plane, 5, 0xffff00 );
// scene.add( helper );

var rocketTarget = new THREE.Vector3();

var cameraTarget = new THREE.Vector3();
cameraTarget.copy( camera.position );

var raycaster = new THREE.Raycaster();

//

var mouse = new THREE.Vector2();

// var targetSphere = new THREE.Mesh(
//  new THREE.SphereBufferGeometry( 0.2 ),
//  new THREE.MeshPhongMaterial({ color: 0x00ff00 })
// );

// scene.add( targetSphere );

function mousemove(e){
  mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
  mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;

  cameraTarget.x = - mouse.x * 1;
  cameraTarget.z = 3 + mouse.y * 1;

  raycaster.setFromCamera( mouse, camera );
  raycaster.ray.intersectPlane( plane, rocketTarget );
  // targetSphere.position.copy( rocketTarget );
}

function mousedown(e){
  event.preventDefault();

  TWEEN.removeAll();

  // rotation
  var dir = mouse.x < 0 ? -1 : 1;
  var tween = new TWEEN.Tween( rocket.rotation )
    .to( { y: dir * Math.PI }, 1000 )
    .easing( TWEEN.Easing.Quadratic.InOut )
    .start();

  // scale
  var tween = new TWEEN.Tween( rocketGroup.scale )
    .to( { x: 0.9, y: 1.2, z: 0.9 }, 400 )
    .easing( TWEEN.Easing.Cubic.InOut )
    .start();

  stars.speedTarget = 0.3;

  renderer.domElement.style.cursor = 'none';
}

function mouseup(e){
  var tween = new TWEEN.Tween( rocketGroup.scale )
    .to( { x: 1, y: 1, z: 1 }, 400 )
    .easing( TWEEN.Easing.Cubic.InOut )
    .start();

  stars.speedTarget = 0.1;
  renderer.domElement.style.cursor = 'pointer';
}

renderer.domElement.addEventListener('mousemove', mousemove, false);
renderer.domElement.addEventListener('mousedown', mousedown, false);
renderer.domElement.addEventListener('mouseup', mouseup, false);

// -------------------------------------------

window.addEventListener( 'resize', resize, false );

function resize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

var clock = new THREE.Clock();
var time = 0;
var angle = THREE.Math.degToRad( 3 );

loop();

function loop() {
  requestAnimationFrame( loop );
  TWEEN.update();
  controls.update();

  time += clock.getDelta();

  rocketGroup.rotation.y = Math.cos( time * 8 ) * angle;

  fireUpdate();

  stars.updateConstant();

  lerp(rocketGroup.position, 'y', rocketTarget.y);
  lerp(rocketGroup.position, 'x', rocketTarget.x);

  lerp(camera.position, 'x', cameraTarget.x);
  lerp(camera.position, 'z', cameraTarget.z);

  lerp(stars, 'speed', stars.speedTarget);

  renderer.render( scene, camera );
}

function lerp( object, prop, destination ) {
  if (object && object[prop] !== destination) {
    object[prop] += (destination - object[prop]) * 0.1;

    if (Math.abs(destination - object[prop]) < 0.01) {
      object[prop] = destination;
    }
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.rawgit.com/mrdoob/three.js/r89/build/three.js
  2. https://cdn.rawgit.com/mrdoob/three.js/r89/examples/js/controls/OrbitControls.js
  3. https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.1.1/Tween.min.js