Skip to content
Permalink
Browse files

Merge pull request #282 from koenbok/feature/perspectiveMatrix

Improve point conversions in layer hierarchy
  • Loading branch information...
koenbok committed Jan 12, 2016
2 parents 631b47b + 00ceb6a commit d4c15a4dcf91aa5963044728be4810c3ebba9602
Showing with 734 additions and 28 deletions.
  1. +80 −14 framer/Layer.coffee
  2. +22 −0 framer/Matrix.coffee
  3. +77 −13 framer/Utils.coffee
  4. +29 −0 test/studio/UtilsConctextFrame.framer/app.coffee
  5. +12 −0 test/studio/UtilsConctextFrame.framer/framer/coffee-script.js
  6. +10 −0 test/studio/UtilsConctextFrame.framer/framer/config.json
  7. +126 −0 test/studio/UtilsConctextFrame.framer/framer/framer.init.js
  8. +1 −0 test/studio/UtilsConctextFrame.framer/framer/framer.js
  9. BIN test/studio/UtilsConctextFrame.framer/framer/images/background.png
  10. BIN test/studio/UtilsConctextFrame.framer/framer/images/cursor.png
  11. BIN test/studio/UtilsConctextFrame.framer/framer/images/cursor@2x.png
  12. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-120.png
  13. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-152.png
  14. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-76.png
  15. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-arrow.png
  16. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-arrow@2x.png
  17. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-close.png
  18. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-close@2x.png
  19. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-framer.png
  20. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-framer@2x.png
  21. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-share.png
  22. BIN test/studio/UtilsConctextFrame.framer/framer/images/icon-share@2x.png
  23. +196 −0 test/studio/UtilsConctextFrame.framer/framer/mirror.css
  24. +44 −0 test/studio/UtilsConctextFrame.framer/framer/style.css
  25. +1 −0 test/studio/UtilsConctextFrame.framer/framer/version
  26. BIN test/studio/UtilsConctextFrame.framer/images/framer-icon.png
  27. +37 −0 test/studio/UtilsConctextFrame.framer/index.html
  28. +76 −0 test/tests/LayerTest.coffee
  29. +23 −1 test/tests/UtilsTest.coffee
@@ -12,6 +12,7 @@ Utils = require "./Utils"
{LayerStyle} = require "./LayerStyle"
{LayerStates} = require "./LayerStates"
{LayerDraggable} = require "./LayerDraggable"
{Matrix} = require "./Matrix"

NoCacheDateKey = Date.now()

@@ -212,6 +213,70 @@ class exports.Layer extends BaseClass
# See: https://github.com/koenbok/Framer/issues/63
@_element.setAttribute "name", value

##############################################################
# Matrices

# matrix of layer transforms
@define "matrix",
get: ->
if @force2d
return @_matrix2d
return new Matrix()
.translate(@x, @y, @z)
.scale(@scale)
.scale(@scaleX, @scaleY, @scaleZ)
.skew(@skew)
.skewX(@skewX)
.skewY(@skewY)
.translate(0, 0, @originZ)
.rotate(@rotationX, 0, 0)
.rotate(0, @rotationY, 0)
.rotate(0, 0, @rotationZ)
.translate(0, 0, -@originZ)

# matrix of layer transforms when 2d is forced
@define "_matrix2d",
get: ->
return new Matrix()
.translate(@x, @y)
.scale(@scale)
.skewX(@skew)
.skewY(@skew)
.rotate(0, 0, @rotationZ)

# matrix of layer transforms with transform origin applied
@define "transformMatrix",
get: ->
return new Matrix()
.translate(@originX * @width, @originY * @height)
.multiply(@matrix)
.translate(-@originX * @width, -@originY * @height)

_perspectiveProjectionMatrix: (element) =>
p = element.perspective
m = new Matrix()
m.m34 = -1/p if p? and p isnt 0
return m

# matrix of perspective projection with perspective origin applied
_perspectiveMatrix: (element) =>
ox = element.perspectiveOriginX * element.width
oy = element.perspectiveOriginY * element.height
ppm = @_perspectiveProjectionMatrix(element)
return new Matrix()
.translate(ox, oy)
.multiply(ppm)
.translate(-ox, -oy)

# matrix of layer transforms with perspective applied
@define "matrix3d",
get: ->
parent = @superLayer or @context
ppm = @_perspectiveMatrix(parent)
return new Matrix()
.multiply(ppm)
.multiply(@transformMatrix)

##############################################################
# Border radius compatibility

@@ -300,32 +365,33 @@ class exports.Layer extends BaseClass
get: -> Utils.frameGetMaxY @
set: (value) -> Utils.frameSetMaxY @, value

convertPoint: (point) ->
# Convert a point on screen to this views coordinate system
# TODO: needs tests
Utils.convertPoint point, null, @
convertPointFromScreen: (point) ->
return Utils.convertPointFromContext(point, @, false)

convertPointFromCanvas: (point) ->
return Utils.convertPointFromContext(point, @, true)

convertPointToScreen: (point) ->
return Utils.convertPointToContext(point, @, false)

convertPointToCanvas: (point) ->
return Utils.convertPointToContext(point, @, true)

@define "canvasFrame",
importable: true
exportable: false
get: ->
Utils.convertPoint(@frame, @, null, context=true)
return Utils.boundingFrame(@)
set: (frame) ->
if not @parent
@frame = frame
else
@frame = Utils.convertPoint(frame, null, @parent, context=true)
@frame = Utils.convertFrameFromContext(frame, @, true, false)

@define "screenFrame",
importable: true
exportable: false
get: ->
Utils.convertPoint(@frame, @, null, context=false)
return Utils.boundingFrame(@, false)
set: (frame) ->
if not @parent
@frame = frame
else
@frame = Utils.convertPoint(frame, null, @parent, context=false)
@frame = Utils.convertFrameFromContext(frame, @, false, false)

contentFrame: ->
return {x:0, y:0, width:0, height:0} unless @children.length
@@ -0,0 +1,22 @@
WebKitCSSMatrix::skew = (skew) ->
if !skew || skew == 0
return @
rad = skew * Math.PI / 180
value = Math.tan(rad)
m = new WebKitCSSMatrix()
m.m12 = value
m.m21 = value
return @multiply(m)

WebKitCSSMatrix::point = (point = {}) ->
x = point.x || 0
y = point.y || 0
z = point.z || 0
w = @m14 * x + @m24 * y + @m34 * z + @m44
w = w || 1
return point =
x: (@m11 * x + @m21 * y + @m31 * z + @m41) / w
y: (@m12 * x + @m22 * y + @m32 * z + @m42) / w
z: (@m13 * x + @m23 * y + @m33 * z + @m43) / w

exports.Matrix = WebKitCSSMatrix
@@ -650,6 +650,40 @@ Utils.framePoint = (frame) ->
x: frame.x
y: frame.y

Utils.pointsFromFrame = (frame) ->
minX = Utils.frameGetMinX(frame)
maxX = Utils.frameGetMaxX(frame)
minY = Utils.frameGetMinY(frame)
maxY = Utils.frameGetMaxY(frame)
corner1 = {x:minX, y:minY}
corner2 = {x:minX, y:maxY}
corner3 = {x:maxX, y:maxY}
corner4 = {x:maxX, y:minY}
return [corner1, corner2, corner3, corner4]

Utils.frameFromPoints = (points) ->

xValues = _.pluck(points, "x")
yValues = _.pluck(points, "y")

minX = _.min(xValues)
maxX = _.max(xValues)
minY = _.min(yValues)
maxY = _.max(yValues)

frame =
x: minX
y: minY
width: maxX - minX
height: maxY - minY

Utils.pixelAlignedFrame = (frame) ->
result =
width: Math.round(frame.width + (frame.x % 1))
height: Math.round(frame.height + (frame.y % 1))
x: Math.round(frame.x)
y: Math.round(frame.y)

Utils.frameMerge = ->

# Return a frame that fits all the input frames
@@ -717,26 +751,56 @@ Utils.pointAngle = (p1, p2) ->

# Coordinate system

Utils.convertPoint = (input, layerA, layerB, context=false) ->
# convert a point from a layer to the context level, with limit you can make it continue to the root context
Utils.convertPointToContext = (point = {}, layer, limit=false) ->
point = _.defaults(point, {x:0, y:0, z:0})
ancestors = layer.ancestors(limit)
ancestors.unshift(layer)

# Convert a point between two layer coordinate systems
for ancestor in ancestors
point.z = 0 if ancestor.flat
point = ancestor.matrix3d.point(point)
point.z = 0 unless ancestor.parent

point = _.defaults(input, {x:0, y:0})
return point

parentsA = layerA?.ancestors(context) or []
parentsB = layerB?.ancestors(context) or []
# convert a point from the context level to a layer, with limit you can make it start from the root context
Utils.convertPointFromContext = (point = {}, layer, limit=false, includeLayer = true) ->
point = _.defaults(point, {x:0, y:0, z:0})
ancestors = layer.ancestors(limit)
point = ancestors.pop().matrix3d.inverse().point(point) if ancestors.length
ancestors.reverse()
ancestors.push(layer) if includeLayer
for ancestor in ancestors
point = ancestor.matrix3d.inverse().point(point)
return point

parentsB.push(layerB) if layerB
# convert a frame from the context level to a layer, with limit you can make it start from the root context
Utils.convertFrameFromContext = (frame, layer, limit=false, includeLayer = true) ->
frame = _.defaults(frame, {x:0, y:0, width:100, height:100})
corners = Utils.pointsFromFrame(frame)
convertedCorners = corners.map (point) =>
return Utils.convertPointFromContext(point, layer, limit, includeLayer)
return Utils.frameFromPoints(convertedCorners)

for layer in parentsA
point.x += layer.x #- layer.scrollFrame.x
point.y += layer.y #- layer.scrollFrame.y
# convert a point from layerA to layerB via the context
Utils.convertPoint = (input, layerA, layerB, rootContext=false) ->

for layer in parentsB
point.x -= layer.x #+ layer.scrollFrame.x
point.y -= layer.y #+ layer.scrollFrame.y
# Convert a point between two layer coordinate systems
point = _.defaults(input, {x:0, y:0, z:0})
point = Utils.convertPointToContext(point, layerA, rootContext) if layerA
return point unless layerB
return Utils.convertPointFromContext(point, layerB, rootContext)

# get the bounding frame of a layer, either at the canvas (rootcontext) or screen level
Utils.boundingFrame = (layer, rootContext=true) ->
frame = {x:0, y:0, width:layer.width, height:layer.height}
cornerPoints = Utils.pointsFromFrame(frame)
contextCornerPoints = cornerPoints.map (point) ->
return Utils.convertPointToContext(point, layer, rootContext)
boundingFrame = Utils.frameFromPoints(contextCornerPoints)
return Utils.pixelAlignedFrame(boundingFrame)

return point

###################################################################
# Beta additions, use with care
@@ -0,0 +1,29 @@
l = new Layer
skew: 30
width: 400
backgroundColor: "gray"
scale: 1.4
l.center()

l.animate
properties: rotation: 360, rotationX: 360
repeat: 10000
time: 16
curve: "linear"

overlay = new Layer
backgroundColor: null
borderColor: "magenta"
borderWidth: 5

l.on "change:rotation", ->
overlay.frame = l.screenFrame

ctx = new Framer.Context name: "root"
ctx.run =>
cl = new Layer
backgroundColor: null
borderColor: "cyan"
borderWidth: 2
l.on "change:rotation", ->
cl.frame = l.canvasFrame

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,10 @@
{
"device" : "iPhone 5S Space Gray",
"sharedPrototype" : 1,
"deviceOrientation" : 0,
"contentScale" : 1,
"deviceType" : "fullscreen",
"updateDelay" : 0.3,
"deviceScale" : -1,
"delay" : 0.3
}
Oops, something went wrong.

0 comments on commit d4c15a4

Please sign in to comment.
You can’t perform that action at this time.