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

              
                <div id="container">
  <div id="composition">
    <h1>Drag Me!</h1>
    <ul class="axis">
      <li class="x">x</li>
      <li class="y">y</li>
      <li class="z">z</li>
    </ul>
    <ul class="key">
      <li class="vertex">Vertex ( V )</li>
      <li class="projection">Plane Projection ( P )</li>
      <li class="nearest">Nearest Point ( N )</li>
      <li class="casting">Casting Point ( C )</li>
    </ul>
    <ul class="calculations">
      <li><span class="vertex">V</span> ● <span class="nearest">N</span> = <span id="a1" class="answer">0.000</span></li>
      <li><span class="vertex">V</span> ● <span class="casting">C</span> = <span id="a2" class="answer">0.000</span></li>
    </ul>
  </div>
</div>
              
            
!

CSS

              
                @import compass

body
  -webkit-font-smoothing: antialiased
  font-family: "Open Sans"
  background: #666

canvas
  display: block
    
h1
  letter-spacing: -1px
  text-align: center
  position: absolute
  font-style: italic
  font-weight: normal
  width: 100%
  color: #CCC

.axis
  position: absolute
  bottom: 20px
  right: 30px
  padding: 0
  margin: 0

  li
    list-style: none

    &:before
      content: "⤑ "

  .x
    color: #FF0000

  .y
    color: #00FF00

  .z
    color: #0000FF

.key
  font-style: italic
  position: absolute
  left: 0
  top: 0

.vertex
  color: #000000

.projection
  color: #FF0044

.nearest
  color: #AA00FF

.casting
  color: #00FF88

.answer
  display: inline-block
  text-align: right
  color: #00AAFF
  width: 4em

.calculations
  font-family: Monaco
  position: absolute
  font-weight: bold
  list-style: none
  font-size: 1.5em
  bottom: 25px
  left: 40px
  color: #BBB
  padding: 0
  margin: 0

#container
  position: relative
  margin: 80px auto
  height: 580px
  width: 1000px

#composition
  position: absolute
  background: #FFF
  height: 100%
  width: 760px
  right: 0
  top: 0

#scene
  position: relative
  z-index: 1

#gui
  position: absolute
  left: 0
  top: 0

              
            
!

JS

              
                
#========================================
# Math Augmentation
#========================================

Math.PI2 = Math.PI * 2
Math.PIH = Math.PI / 2
Math.PIQ = Math.PI / 4
Math.PIR = 180 / Math.PI
Math.PID = Math.PI / 180
Math.degreesToRadians = (degrees) -> degrees * Math.PID



#========================================
# Core Objects & Variables
#========================================

GUI = 240
UNIT = 10
SCALE = 0.06
DISC = UNIT*0.1

# DOM Elements
container = document.getElementById 'container'
composition = document.getElementById 'composition'
answer1 = document.getElementById 'a1'
answer2 = document.getElementById 'a2'

# Dimensions
width = container.offsetWidth - GUI
height = container.offsetHeight
halfX = width * SCALE
halfY = height * SCALE

# ThreeJS Core Objects
camera = new THREE.OrthographicCamera -halfX, halfX, halfY, -halfY, 1, 1000
camera.position.z = 500
renderer = new THREE.WebGLRenderer antialias:true
renderer.domElement.setAttribute 'id', 'scene'
renderer.setSize width, height
scene = new THREE.Scene
clock = new THREE.Clock

# ThreeJS Scene Objects
origin = new THREE.Object3D
axis = new THREE.AxisHelper UNIT

# ThreeJS Scene Controls
trackball = new TrackballControls origin, renderer.domElement
trackball.pan.enabled = false

# GUI Controls
gui = new dat.GUI autoPlace:false, resizable:false, width:GUI
gui.domElement.setAttribute 'id', 'gui'



#========================================
# Light Object
#========================================

COLOR = 0xFFDD00
light = new THREE.Object3D
light.size = new THREE.Vector2 UNIT*4, UNIT*3
light.normal = new THREE.Vector3 0, 0, 1
light.right = new THREE.Vector3 1, 0, 0
light.up = new THREE.Vector3 0, 1, 0
light.normalMatrix = new THREE.Matrix3
geometry = new THREE.PlaneGeometry 1, 1, 1, 1
material = new THREE.MeshBasicMaterial
  side: THREE.DoubleSide
  transparent: true
  opacity: 0.4
  color: COLOR
light.plane = new THREE.Mesh geometry, material
light.add light.plane
geometry = new THREE.CylinderGeometry 0, (Math.sin Math.PIQ), UNIT, 4, 1, false
material = new THREE.MeshBasicMaterial
  transparent: true
  wireframe: true
  opacity: 0.8
  color: COLOR
matrix = new THREE.Matrix4
matrix.setPosition x:0, y:0, z:UNIT*-0.5
matrix.rotateX -Math.PIH
matrix.rotateY Math.PIQ
geometry.applyMatrix matrix
light.frame = new THREE.Mesh geometry, material
light.add light.frame



#========================================
# Vertex Object
#========================================

vertex = new THREE.Object3D
vertex.position.set UNIT*-3, UNIT*1, UNIT*4
vertex.normal = new THREE.Vector3 0, 1, 0
vertex.arrow = new THREE.ArrowHelper (new THREE.Vector3), (new THREE.Vector3), UNIT, 0x000000
vertex.add vertex.arrow
geometry = new THREE.CircleGeometry UNIT, 16
material = new THREE.MeshBasicMaterial
  transparent: true
  wireframe: true
  color: 0x000000
  opacity: 0.4
vertex.plane = new THREE.Mesh geometry, material
vertex.plane.rotation.x = Math.PIH
vertex.add vertex.plane



#========================================
# Projection Object Helper
#========================================

createProjectionObject = (color, opacity) ->
  object = new THREE.Object3D
  object['location'] = new THREE.Vector3
  object['direction'] = new THREE.Vector3
  geometry = new THREE.CircleGeometry DISC, 16
  material = new THREE.MeshBasicMaterial
    transparent: true
    wireframe: true
    opacity: opacity
    color: color
  object['disc'] = new THREE.Mesh geometry, material
  object.add object.disc
  geometry = new THREE.Geometry
  geometry.vertices.push new THREE.Vector3 0, 0, 0
  geometry.vertices.push new THREE.Vector3 0, 0, 0
  material = new THREE.LineBasicMaterial
    transparent: true
    opacity: opacity
    color: color
  object['line'] = new THREE.Line geometry, material
  object.add object.line
  return object



#========================================
# Projection Objects
#========================================

planeProjection = createProjectionObject 0xFF0044, 0.8

nearestPoint = createProjectionObject 0xAA00FF, 0.8
nearestPoint.offset = new THREE.Vector3

castingPoint = createProjectionObject 0x00FF88, 0.8
castingPoint.offset = new THREE.Vector3



#========================================
# Setup
#========================================

initialise = () ->
  composition.appendChild renderer.domElement
  container.appendChild gui.domElement

  # Add Scene Objects
  scene.add origin
  origin.add axis
  origin.add light
  origin.add vertex
  origin.add planeProjection
  origin.add nearestPoint
  origin.add castingPoint
  
  # Add GUI Controls
  folder = gui.addFolder 'LIGHT'
  addPositionControls folder, light
  addRotationControls folder, light
  addSizeControls folder, light
  folder.open()
  folder = gui.addFolder 'VERTEX'
  addPositionControls folder, vertex
  addRotationControls folder, vertex
  folder.open()
  return

animate = () ->
  requestAnimationFrame animate
  delta = clock.getDelta()
  time = clock.elapsedTime
  trackball.update()
  renderer.render scene, camera
  return



#========================================
# GUI Control Helpers
#========================================

addPositionControls = (folder, object) ->
  object.positionX = object.position.x
  object.positionY = object.position.y
  object.positionZ = object.position.z
  object.resetPosition = () =>
    object.position.set 0, 0, 0
    object.pxc.setValue 0
    object.pyc.setValue 0
    object.pzc.setValue 0
  object.pxc = folder.add object, 'positionX', -100, 100, 1
  object.pxc.onChange (value) ->
    object.position.setX value
    update()
  object.pyc = folder.add object, 'positionY', -100, 100, 1
  object.pyc.onChange (value) ->
    object.position.setY value
    update()
  object.pzc = folder.add object, 'positionZ', -100, 100, 1
  object.pzc.onChange (value) ->
    object.position.setZ value
    update()
  object.rpc = folder.add object, 'resetPosition'
  object.rpc.onChange (value) -> update()
  return

addRotationControls = (folder, object) ->
  object.rotationX = object.rotation.x
  object.rotationY = object.rotation.y
  object.rotationZ = object.rotation.z
  object.resetRotation = () =>
    object.rotation.set 0, 0, 0
    object.rxc.setValue 0
    object.ryc.setValue 0
    object.rzc.setValue 0
  object.rxc = folder.add object, 'rotationX', -180, 180, 1
  object.rxc.onChange (value) ->
    object.rotation.setX Math.degreesToRadians value
    update()
  object.ryc = folder.add object, 'rotationY', -180, 180, 1
  object.ryc.onChange (value) ->
    object.rotation.setY Math.degreesToRadians value
    update()
  object.rzc = folder.add object, 'rotationZ', -180, 180, 1
  object.rzc.onChange (value) ->
    object.rotation.setZ Math.degreesToRadians value
    update()
  object.rrc = folder.add object, 'resetRotation'
  object.rrc.onChange (value) -> update()
  return

addSizeControls = (folder, object) ->
  object.width = object.size.x
  object.height = object.size.y
  object.sxc = folder.add object, 'width', UNIT*0.1, UNIT*10, 1
  object.sxc.onChange (value) ->
    object.size.setX value
    update()
  object.syc = folder.add object, 'height', UNIT*0.1, UNIT*10, 1
  object.syc.onChange (value) ->
    object.size.setY value
    update()
  return



#========================================
# Calculations
#========================================

update = () ->

  # Update Light
  light.updateMatrix()
  light.right.set 1, 0, 0
  light.right.transformDirection light.matrix
  light.up.set 0, 1, 0
  light.up.transformDirection light.matrix
  light.normal.set 0, 0, 1
  light.normal.transformDirection light.matrix
  light.plane.scale.set light.size.x, light.size.y, 1
  light.frame.scale.set light.size.x, light.size.y, 1
  light.halfX = light.size.x / 2
  light.halfY = light.size.y / 2

  # Update Vertex
  vertex.updateMatrix()
  vertex.normal.set 0, 1, 0
  vertex.normal.transformDirection vertex.matrix

  # Calculate Vertex Projection
  projectOnPlane planeProjection.location, vertex.position, light.position, light.normal
  planeProjection.line.geometry.vertices[0].copy planeProjection.location
  planeProjection.line.geometry.vertices[1].copy vertex.position
  planeProjection.line.geometry.verticesNeedUpdate = true
  planeProjection.disc.position.copy planeProjection.location
  planeProjection.disc.rotation.copy light.rotation

  # Calculate Nearest Point
  nearestPoint.offset.subVectors planeProjection.location, light.position
  nx = nearestPoint.offset.dot light.right
  nx = Math.max nx, -light.halfX
  nx = Math.min nx,  light.halfX
  ny = nearestPoint.offset.dot light.up
  ny = Math.max ny, -light.halfY
  ny = Math.min ny,  light.halfY
  nearestPoint.location.copy light.position
  nearestPoint.location.add ( nearestPoint.offset.copy light.right ).multiplyScalar nx
  nearestPoint.location.add ( nearestPoint.offset.copy light.up    ).multiplyScalar ny
  nearestPoint.line.geometry.vertices[0].copy nearestPoint.location
  nearestPoint.line.geometry.vertices[1].copy vertex.position
  nearestPoint.line.geometry.verticesNeedUpdate = true
  nearestPoint.disc.position.copy nearestPoint.location
  nearestPoint.disc.rotation.copy light.rotation

  
  
  # * * * * * * * * * * * * #
  # Calculate Casting Point #
  # * * * * * * * * * * * * #

  # 1) Surface Vertex:
  #    vertex.position – Vector3D
  #    vertex.normal   – Vector3D ( UNIT )

  # 2) Light:
  #    light.position  – Vector3D
  #    light.normal    – Vector3D ( UNIT )
  #    light.right     – Vector3D ( UNIT )
  #    light.up        – Vector3D ( UNIT )
  #    light.size      – Vector2D where size.x is the WIDTH and size.y is the HEIGHT

  castingPoint.location.copy light.position # FIXME!!!
  castingPoint.line.geometry.vertices[0].copy castingPoint.location
  castingPoint.line.geometry.vertices[1].copy vertex.position
  castingPoint.line.geometry.verticesNeedUpdate = true
  castingPoint.disc.position.copy castingPoint.location
  castingPoint.disc.rotation.copy light.rotation

  
  
  # Update Calculations
  (nearestPoint.direction.subVectors nearestPoint.location, vertex.position).normalize()
  a1.innerText = (vertex.normal.dot nearestPoint.direction).toFixed 3

  (castingPoint.direction.subVectors castingPoint.location, vertex.position).normalize()
  a2.innerText = (vertex.normal.dot castingPoint.direction).toFixed 3
  return

projectOnPlane = (output, point, planeCenter, planeNormal) ->
  # https://github.com/mrdoob/three.js/blob/master/examples/js/ShaderDeferred.js#L959
  # point - dot(point - planeCenter, planeNormal) * planeNormal;
  output.copy point
  output.sub planeCenter
  distance = output.dot planeNormal
  output.copy planeNormal
  output.multiplyScalar distance
  output.subVectors point, output
  return



initialise()
update()
animate()

              
            
!
999px

Console