Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h1>UFO with Controls</h1>

<p> Current time: <span id="time"></span></p>

<ul>
  <li>0:  reset animation to initial state
  <li>1:  take one step
  <li>g:  "go" &mdash; start the animation <q>loop</q>
  <li>SPC:  stop the animation
</ul>

              
            
!

CSS

              
                body {
  max-width: 100%;
}
/* feel free to style the canvas any way you want. If you want it to
      use the entire window, set width: 100% and height: 100%. */

canvas {
  width: 80%;
  height: 500px;
  display: block;
  margin: 10px auto;
}

              
            
!

JS

              
                TW.randomBell = function (center, range) {
  // returns a sample from an approximately bell-shaped distribution with
  // the input center and range of values
  var i,
    sum = 0;
  for (i = 0; i < range; i++) {
    sum += 2 * Math.random() - 1;
  }
  return center + sum;
};

var halfSize = 50; // half the extent of the scene

// global parameters (only a few are controlled in the gui)
var guiParams = {
  ufoSize: 1, // used to scale UFO size - really 10x5x10
  gunLength: 20,
  torpedoVelocity: 100, // speed of torpedoes, set in gui
  initX: -48, // initial position of the UFO
  initY: 80, // height does not change in animation
  initZ: -25,
  initVx: 1.2, // initial velocity of UFO, set in gui
  initVz: -0.3,
  later: 20, // time when UFO velocity changes, set in gui
  laterVx: 2.1, // later velocity, set in gui
  laterVz: 1,
  deltaT: 0.05 // time between steps, arbitrary time units, set in gui
};

// ================================================================

var scene = new THREE.Scene();

var renderer = new THREE.WebGLRenderer();

TW.mainInit(renderer, scene);

TW.cameraSetup(renderer, scene, {
  minx: -halfSize,
  maxx: halfSize,
  miny: 0,
  maxy: 90,
  minz: -halfSize,
  maxz: halfSize
});

var ufoObj; // container for the UFO (flattened sphere) and the gun
var gun; // just a THREE.Line object with two vertices
var photonTorpedo; // a tiny sphere

// changes the "to" vertex of the line to define the direction of aim of the gun

function aimGun(gun, dx, dz) {
  var geom = gun.geometry;
  var v1 = geom.vertices[1];
  v1.set(dx, -guiParams.gunLength, dz);
  geom.verticesNeedUpdate = true;
}

// creates a THREE.Line object from two vertices "from" and "to"

function line(from, to, color) {
  var geom = new THREE.Geometry();
  geom.vertices = [from, to];
  var mat = new THREE.LineBasicMaterial({ color: color, linewidth: 2 });
  var lineObj = new THREE.Line(geom, mat);
  return lineObj;
}

// creates the scene, with barn, ground, UFO object with gun, and photon torpedo

function makeScene() {
  // create barn and ground, and add to scene
  var barn = new TW.createMesh(TW.createBarn(30, 40, 50));
  scene.add(barn);

  var ground = new THREE.Mesh(
    new THREE.PlaneGeometry(2 * halfSize, 2 * halfSize),
    new THREE.MeshBasicMaterial({ color: THREE.ColorKeywords.darkgreen })
  );
  ground.rotation.x = -Math.PI / 2;
  scene.add(ground);

  // create UFO object with gun, and add to scene
  ufoObj = new THREE.Object3D();
  var ufo = new THREE.Mesh(
    new THREE.SphereGeometry(guiParams.ufoSize),
    new THREE.MeshBasicMaterial({ color: THREE.ColorKeywords.purple })
  );
  ufo.scale.set(10, 5, 10); // real size of UFO
  gun = line(
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, -guiParams.gunLength, 0),
    THREE.ColorKeywords.purple
  );
  ufoObj.add(ufo);
  ufoObj.add(gun);
  ufoObj.position.set(guiParams.initX, guiParams.initY, guiParams.initZ);
  scene.add(ufoObj);

  // create photon torpedo and add to scene
  photonTorpedo = new THREE.Mesh(
    new THREE.SphereGeometry(1),
    new THREE.MeshBasicMaterial({ color: THREE.ColorKeywords.yellow })
  );
  photonTorpedo.visible = false;
  scene.add(photonTorpedo);
}

makeScene();

// state variables of the animation

var animationState;

// reset animation to initial state

function resetAnimationState() {
  animationState = {
    time: 0,
    x: guiParams.initX, // current position of UFO
    z: guiParams.initZ,
    vx: guiParams.initVx, // current velocity of UFO
    vz: guiParams.initVz
  };
  ufoObj.position.x = animationState.x;
  ufoObj.position.z = animationState.z;
  document.getElementById("time").innerHTML = animationState.time;
}

// resets the animation to the initial state and renders the scene

function firstState() {
  resetAnimationState();
  TW.render();
}

// updates the state of the animation

function updateState() {
  // changes the time and everything that depends on time
  var dt = guiParams.deltaT;
  animationState.time += dt;
  document.getElementById("time").innerHTML = animationState.time.toFixed(3);
  // change velocity after the time given by guiParams.later
  // (actually sets them multiple times, but no harm)
  if (animationState.time > guiParams.later) {
    animationState.vx = guiParams.laterVx;
    animationState.vz = guiParams.laterVz;
  }
  // change UFO location
  animationState.x += animationState.vx * dt;
  animationState.z += animationState.vz * dt;
  ufoObj.position.x = animationState.x;
  ufoObj.position.z = animationState.z;
  // stop the animation when UFO reaches outer bounds of scene (generalizes to
  // movement in +/- X and Z directions)
  if (
    animationState.x < -halfSize ||
    animationState.x > +halfSize ||
    animationState.z < -halfSize ||
    animationState.z > +halfSize
  ) {
    console.log("stop because out of bounds");
    stopAnimation();
  }
  var time = animationState.time;
  if (Math.floor(time) <= time && time <= Math.floor(time) + dt) {
    // re-aim and fire a new photon torpedo every second - the code above tests
    // for the beginning (first animation frame) of a second, assuming dt < 1
    var torpedoDir = new THREE.Vector3(0, -guiParams.gunLength, 0);
    torpedoDir.x = TW.randomBell(0, guiParams.gunLength);
    torpedoDir.z = TW.randomBell(0, guiParams.gunLength);
    // update where the gun is aiming
    aimGun(gun, torpedoDir.x, torpedoDir.z);
    animationState.photonSequence = 0;
    var start = new THREE.Vector3();
    start.copy(ufoObj.position);
    animationState.torpedoStart = start;
    animationState.torpedoStartTime = time;
    animationState.torpedoDir = torpedoDir;
  } else {
    // draw a photon torpedo in the scene at Q = P + V * t
    animationState.photonSequence++;
    var P = animationState.torpedoStart;
    // absolute time
    var sinceStart = time - animationState.torpedoStartTime;
    var Q = new THREE.Vector3();
    var V = new THREE.Vector3();
    V.copy(animationState.torpedoDir);
    V.normalize();
    V.multiplyScalar(guiParams.torpedoVelocity * sinceStart);
    Q.addVectors(P, V);
    photonTorpedo.position.copy(Q);
    photonTorpedo.visible = true;
  }
}

// performs one step of the animation

function oneStep() {
  updateState();
  TW.render();
}

var stopRequested = false; // set to true to have the animation stop itself

var animationId = null; // so we can cancel the animation if we want

// starts continuous animation loop

function animate() {
  oneStep();
  if (!stopRequested) {
    animationId = requestAnimationFrame(animate);
  }
}

function startAnimation() {
  stopRequested = false;
  if (animationId == null) {
    animate();
  }
}

// halts the animation

function stopAnimation() {
  if (animationId != null) {
    cancelAnimationFrame(animationId);
    stopRequested = true;
    animationId = null;
  }
}

// setup keyboard callbacks

TW.setKeyboardCallback("0", firstState, "reset animation");
TW.setKeyboardCallback("1", oneStep, "advance by one step");
TW.setKeyboardCallback("g", startAnimation, "go:  start animation");
TW.setKeyboardCallback(" ", stopAnimation, "stop animation");

// setup gui controls

// listen() method enables gui controller to be adjusted automatically if
// the code execution results in a change to the parameter

var gui = new dat.GUI();
gui.add(guiParams, "deltaT", 0.01, 0.99).step(0.01).listen();
gui.add(guiParams, "later", 1, 40).step(0.1).listen();
gui.add(guiParams, "torpedoVelocity", 50, 200).step(1).listen();
gui.add(guiParams, "initVx", 1, 10).step(0.1).listen();
gui.add(guiParams, "laterVx", 1, 10).step(0.1).listen();

firstState();

              
            
!
999px

Console