cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

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.

            
              body {
  background-color: #cccccc;
}
canvas {
  background-color: #efefef;
}
            
          
!
            
              ###
Usage:
  new Visibility
Whenever map data changes:
  loadMap
Whenever light source changes:
  setLightLocation
To calculate the area:
  sweep

based on: https://simblob.blogspot.com/2012/07/2d-visibility.html
###
class Visibility
  constructor: ->
    @segments = []
    @endpoints = []
    @open = []
    @center = new Point 0, 0
    @output = []

  loadMap: (size, margin, blocks, walls) ->
    @segments = []
    @endpoints = []
    @loadEdgeOfMap size, margin
    for {x, y, r} in blocks
      @addSegment x - r, y - r, x - r, y + r
      @addSegment x - r, y + r, x + r, y + r
      @addSegment x + r, y + r, x + r, y - r
      @addSegment x + r, y - r, x - r, y - r
    for {p1, p2} in walls
      @addSegment p1.x, p1.y, p2.x, p2.y

  loadEdgeOfMap: (size, margin) ->
    @addSegment margin, margin, margin, size - margin
    @addSegment margin, size - margin, size - margin, size - margin
    @addSegment size - margin, size - margin, size - margin, margin
    @addSegment size - margin, margin, margin, margin

  addSegment: (x1, y1, x2, y2) ->
    segment = null
    p1 = new EndPoint 0, 0
    p1.segment = segment
    p1.visualize = true
    p2 = new EndPoint 0, 0
    p2.segment = segment
    p2.visualize = false
    segment = new Segment
    p1.x = x1
    p1.y = y1
    p2.x = x2
    p2.y = y2
    p1.segment = segment
    p2.segment = segment
    segment.p1 = p1
    segment.p2 = p2
    segment.d = 0
    @segments.push segment
    @endpoints.push p1
    @endpoints.push p2

  setLightLocation: (x, y) ->
    @center.x = x
    @center.y = y
    for segment in @segments
      dx = 0.5 * (segment.p1.x + segment.p2.x) - x
      dy = 0.5 * (segment.p1.y + segment.p2.y) - y
      segment.d = dx * dx + dy * dy
      segment.p1.angle = Math.atan2 segment.p1.y - y, segment.p1.x - x
      segment.p2.angle = Math.atan2 segment.p2.y - y, segment.p2.x - x
      dAngle = segment.p2.angle - segment.p1.angle
      dAngle += 2 * Math.PI if dAngle <= 0 - Math.PI
      dAngle -= 2 * Math.PI if dAngle > Math.PI
      segment.p1.begin = dAngle > 0
      segment.p2.begin = !segment.p1.begin

  sweep: (maxAngle = 999) ->
    @output = []
    @endpoints.sort endpointCompare
    @open = []
    beginAngle = 0
    for pass in [0...2]
      for p in @endpoints
        break if pass is 1 and p.angle > maxAngle
        currentOld = if @open.length is 0 then null else @open[0]
        if p.begin
          node = @open[0]
          nodeIndex = 0
          while node and segmentInFrontOf p.segment, node, @center
            nodeIndex++
            node = @open[nodeIndex]
          unless node
            @open.push p.segment
          else
            @open.splice nodeIndex, 0, p.segment
        else
          segmentIndex = @open.indexOf p.segment
          @open.splice segmentIndex, 1
        currentNew = if @open.length is 0 then null else @open[0]
        if currentOld isnt currentNew
          if pass is 1
            @addTriangle beginAngle, p.angle, currentOld
          beginAngle = p.angle

  addTriangle: (angle1, angle2, segment) ->
    p1 = @center
    p2 = new Point @center.x + Math.cos(angle1), @center.y + Math.sin(angle1)
    p3 = new Point 0, 0
    p4 = new Point 0, 0
    if segment
      p3.x = segment.p1.x
      p3.y = segment.p1.y
      p4.x = segment.p2.x
      p4.y = segment.p2.y
    else
      p3.x = @center.x + Math.cos(angle1) * 500
      p3.y = @center.y + Math.sin(angle1) * 500
      p4.x = @center.x + Math.cos(angle2) * 500
      p4.y = @center.y + Math.sin(angle2) * 500
    pBegin = lineIntersection p3, p4, p1, p2
    p2.x = @center.x + Math.cos angle2
    p2.y = @center.y + Math.sin angle2
    pEnd = lineIntersection p3, p4, p1, p2
    @output.push pBegin
    @output.push pEnd

class Block

class Point
  constructor: (@x, @y) ->

class EndPoint extends Point
  constructor: (x, y) ->
    super x, y
    @begin = false
    @angle = 0
    @visualize = false

class Segment

endpointCompare = (a, b) ->
  return 1 if a.angle > b.angle
  return 0 - 1 if a.angle < b.angle
  return 1 if !a.begin and b.begin
  return 0 - 1 if a.begin and !b.begin
  0

segmentInFrontOf = (a, b, relativeTo) ->
  A1 = leftOf a, interpolate b.p1, b.p2, 0.01
  A2 = leftOf a, interpolate b.p2, b.p1, 0.01
  A3 = leftOf a, relativeTo
  B1 = leftOf b, interpolate a.p1, a.p2, 0.01
  B2 = leftOf b, interpolate a.p2, a.p1, 0.01
  B3 = leftOf b, relativeTo
  return true if B1 is B2 and B2 isnt B3
  return true if A1 is A2 and A2 is A3
  return false if A1 is A2 and A2 isnt A3
  return false if B1 is B2 and B2 is B3
  false

lineIntersection = (p1, p2, p3, p4) ->
  s = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y))
  new Point p1.x + s * (p2.x - p1.x), p1.y + s * (p2.y - p1.y)

leftOf = (s, p) ->
  cross = (s.p2.x - s.p1.x) * (p.y - s.p1.y) - (s.p2.y - s.p1.y) * (p.x - s.p1.x)
  cross < 0

interpolate = (p, q, f) ->
  new Point p.x * (1 - f) + q.x * f, p.y * (1 - f) + q.y * f

# Demo content
mazeWalls = [
  # Horizontal walls
  [20, 60, 60, 60], [60, 60, 100, 60], [100, 60, 140, 60], [140, 60, 180, 60],
  [60, 100, 100, 100], [100, 100, 140, 100],
  [260, 100, 300, 100], [300, 100, 340, 100],
  [140, 140, 180, 140], [180, 140, 220, 140],
  [300, 140, 340, 140], [340, 140, 380, 140],
  [140, 260, 180, 260], [180, 260, 220, 260],
  [215, 240, 225, 240], [260, 220, 275, 220],
  # Vertical walls
  [300, 20, 300, 60],
  [180, 60, 180, 100], [180, 100, 180, 140],
  [260, 60, 260, 100], [340, 60, 340, 100],
  [180, 140, 180, 180], [180, 180, 180, 220],
  [260, 140, 260, 180], [260, 180, 260, 220],
  [140, 220, 140, 260], [140, 260, 140, 300], [140, 300, 140, 340],
  [220, 240, 220, 260], [220, 340, 220, 380],
  # Wall with holes
  [220, 260, 220, 268], [220, 270, 220, 278], [220, 280, 220, 288],
  [220, 290, 220, 298], [220, 300, 220, 308], [220, 310, 220, 318],
  [220, 320, 220, 328], [220, 330, 220, 338],
  # Pillars
  [210, 70, 230, 70], [230, 70, 230, 90], [230, 90, 222, 90], [218, 90, 210, 90], [210, 90, 210, 70],
  [51, 240, 60, 231], [60, 231, 69, 240], [69, 240, 60, 249], [60, 249, 51, 240],
  # Curves
  [20, 140, 50, 140], [50, 140, 80, 150], [80, 150, 95, 180], [95, 180, 100, 220],
  [100, 220, 100, 260], [100, 260, 95, 300], [95, 300, 80, 330],
  [300, 180, 320, 220], [320, 220, 320, 240], [320, 240, 310, 260],
  [310, 260, 305, 275], [305, 275, 300, 300], [300, 300, 300, 310],
  [300, 310, 305, 330], [305, 330, 330, 350], [330, 350, 360, 360]
]

interpretSvg = (g, path) ->
  i = 0
  while i < path.length
    if path[i] == 'M'
      g.mt path[i + 1], path[i + 2]
      i += 2
    if path[i] == 'L'
      g.lt path[i + 1], path[i + 2]
      i += 2
    i++

computeVisibleAreaPaths = (center, output) ->
  path1 = []
  path2 = []
  path3 = []
  i = 0
  while i < output.length
    p1 = output[i]
    p2 = output[i + 1]
    if isNaN(p1.x) or isNaN(p1.y) or isNaN(p2.x) or isNaN(p2.y)
      i += 2
      continue
    path1.push 'L', p1.x, p1.y, 'L', p2.x, p2.y
    path2.push 'M', center.x, center.y, 'L', p1.x, p1.y, 'M', center.x, center.y, 'L', p2.x, p2.y
    path3.push 'M', p1.x, p1.y, 'L', p2.x, p2.y
    i += 2
  {
    floor: path1
    triangles: path2
    walls: path3
  }

drawFloor = (g, path, center) ->
  g.f '#ffffff'
  g.mt center.x, center.y
  interpretSvg g, path
  g.lt center.x, center.y
  g.cp()

drawWalls = (g, path) ->
  g.s '#333333'
  g.ss 2
  interpretSvg g, path
  g.cp()

stageEl = new createjs.Stage 'maze'

makeDiagram = (canvasId, options, center, blocks, walls) ->
  gridMargin = 0
  visibility = new Visibility
  wallPoints = walls.map (wall) ->
    {p1:{x:wall[0],y:wall[1]},p2:{x:wall[2],y:wall[3]}}
  visibility.loadMap options.size, gridMargin, [], wallPoints
  g = new createjs.Graphics
  viewEl = new createjs.Shape g
  stageEl.addChild viewEl
  drawAll g, visibility, center
  document.body.addEventListener 'mousemove', (e) ->
    center = {x: e.offsetX, y: e.offsetY}
    drawAll g, visibility, center

drawAll = (g, visibility, center) ->
  visibility.setLightLocation center.x ,center.y
  visibility.sweep Math.PI
  paths = computeVisibleAreaPaths center, visibility.output
  g.c()
  drawFloor g, paths.floor, center
  drawWalls g, paths.walls

makeDiagram 'maze', {size:400}, {x:200,y:200,r:10},[],mazeWalls

createjs.Ticker.setFPS 60
createjs.Ticker.addEventListener 'tick', ->
  stageEl.update()

            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console