CodePen

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

            
              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

            
          
!
? ?
? ?
Must be a valid URL.
+ add another resource
via CSS Lint

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()

            
          
!
Must be a valid URL.
+ add another resource
via JS Hint
Loading ..................