agt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +7 -0
  3. data/README.md +5 -0
  4. data/app/assets/javascripts/agt/config.coffee +9 -0
  5. data/app/assets/javascripts/agt/dom.coffee +12 -0
  6. data/app/assets/javascripts/agt/function.coffee +317 -0
  7. data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
  8. data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
  9. data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
  10. data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
  11. data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
  12. data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
  13. data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
  14. data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
  15. data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
  16. data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
  17. data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
  18. data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
  19. data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
  20. data/app/assets/javascripts/agt/geom/point.coffee +454 -0
  21. data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
  22. data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
  23. data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
  24. data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
  25. data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
  26. data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
  27. data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
  28. data/app/assets/javascripts/agt/impulse.coffee +105 -0
  29. data/app/assets/javascripts/agt/index.coffee +56 -0
  30. data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
  31. data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
  32. data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
  33. data/app/assets/javascripts/agt/inheritance.coffee +132 -0
  34. data/app/assets/javascripts/agt/math.coffee +45 -0
  35. data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
  36. data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
  37. data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
  38. data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
  39. data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
  40. data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
  41. data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
  42. data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
  43. data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
  44. data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
  45. data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
  46. data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
  47. data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
  48. data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
  49. data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
  50. data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
  51. data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
  52. data/app/assets/javascripts/agt/net/router.coffee +165 -0
  53. data/app/assets/javascripts/agt/object.coffee +9 -0
  54. data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
  55. data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
  56. data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
  57. data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
  58. data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
  59. data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
  60. data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
  61. data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
  62. data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
  63. data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
  64. data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
  65. data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
  66. data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
  67. data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
  68. data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
  69. data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
  70. data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
  71. data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
  72. data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
  73. data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
  74. data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
  75. data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
  76. data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
  77. data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
  78. data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
  79. data/app/assets/javascripts/agt/particles/system.coffee +103 -0
  80. data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
  81. data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
  82. data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
  83. data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
  84. data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
  85. data/app/assets/javascripts/agt/promise.coffee +214 -0
  86. data/app/assets/javascripts/agt/random/random.coffee +100 -0
  87. data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
  88. data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
  89. data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
  90. data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
  91. data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
  92. data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
  93. data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
  94. data/app/assets/javascripts/agt/signal.coffee +272 -0
  95. data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
  96. data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
  97. data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
  98. data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
  99. data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
  100. data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
  101. data/lib/agt.rb +8 -0
  102. data/lib/agt/version.rb +5 -0
  103. metadata +173 -0
@@ -0,0 +1,9 @@
1
+ unless Object.getPropertyDescriptor?
2
+ if Object.getPrototypeOf? and Object.getOwnPropertyDescriptor?
3
+ Object.getPropertyDescriptor = (o, name) ->
4
+ proto = o
5
+ descriptor = undefined
6
+ proto = Object.getPrototypeOf?(proto) or proto.__proto__ while proto and not (descriptor = Object.getOwnPropertyDescriptor(proto, name))
7
+ descriptor
8
+ else
9
+ Object.getPropertyDescriptor = -> undefined
@@ -0,0 +1,7 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ prepare: (@bias, @biasInSeconds, @time) ->
@@ -0,0 +1,14 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.DieOnSurface
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@surfaces) ->
8
+ if Object::toString.call(@surface).indexOf('Array') is -1
9
+ @surfaces = [@surfaces]
10
+
11
+ prepare: ->
12
+
13
+ process: (p) ->
14
+ return p.die() for surface in @surfaces when surface.contains p.position
@@ -0,0 +1,11 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.Force extends agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@vector=new agt.geom.Point) ->
8
+
9
+ process: (particle) ->
10
+ particle.velocity.x += @vector.x * @biasInSeconds
11
+ particle.velocity.y += @vector.y * @biasInSeconds
@@ -0,0 +1,14 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.Friction extends agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@amount=1) ->
8
+
9
+ process: (particle) ->
10
+ fx = particle.velocity.x * @biasInSeconds * @amount
11
+ fy = particle.velocity.y * @biasInSeconds * @amount
12
+
13
+ particle.velocity.x -= fx
14
+ particle.velocity.y -= fy
@@ -0,0 +1,9 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.Live extends agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ process: (particle) ->
8
+ particle.life += @bias
9
+ particle.die() if particle.life >= particle.maxLife
@@ -0,0 +1,13 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.MacroAction extends agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@actions=[]) ->
8
+
9
+ prepare: (bias, biasInSeconds, time) ->
10
+ action.prepare bias, biasInSeconds, time for action in @actions
11
+
12
+ process: (particle) ->
13
+ action.process particle for action in @actions
@@ -0,0 +1,11 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.Move extends agt.particles.actions.BaseAction
4
+
5
+ ### Public ###
6
+
7
+ process: (particle) ->
8
+ particle.lastPosition.x = particle.position.x
9
+ particle.lastPosition.y = particle.position.y
10
+ particle.position.x += particle.velocity.x * @biasInSeconds
11
+ particle.position.y += particle.velocity.y * @biasInSeconds
@@ -0,0 +1,9 @@
1
+ namespace('agt.particles.actions')
2
+ # Public:
3
+ class agt.particles.actions.NullAction
4
+
5
+ ### Public ###
6
+
7
+ prepare: ->
8
+
9
+ process: ->
@@ -0,0 +1,17 @@
1
+ namespace('agt.particles.counters')
2
+ # Public:
3
+ class agt.particles.counters.ByRate
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@rate=1) ->
8
+ @count = 0
9
+ @rest = 0
10
+ @offset = 1
11
+
12
+ prepare: (bias, biasInSeconds, time) ->
13
+ @rest += biasInSeconds * @rate
14
+ @count = Math.floor(@rest)
15
+ @rest -= @count
16
+ @count += @offset
17
+ @offset = 0
@@ -0,0 +1,9 @@
1
+ namespace('agt.particles.counters')
2
+ # Public:
3
+ class agt.particles.counters.Fixed
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@count=1) ->
8
+
9
+ prepare: ->
@@ -0,0 +1,9 @@
1
+ namespace('agt.particles.counters')
2
+ # Public:
3
+ class agt.particles.counters.NullCounter
4
+
5
+ ### Public ###
6
+
7
+ count: 0
8
+
9
+ prepare: ->
@@ -0,0 +1,35 @@
1
+ namespace('agt.particles')
2
+
3
+ # Public:
4
+ class agt.particles.Emission
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@particleType=agt.particles.Particle,
9
+ @emitter=new agt.particles.timers.NullEmitter(),
10
+ @timer=new agt.particles.counters.NullTimer(),
11
+ @counter=new agt.particles.emitters.NullCounter(),
12
+ @initializer=null) ->
13
+
14
+ prepare: (bias, biasInSeconds, time) ->
15
+ @timer.prepare bias, biasInSeconds, time
16
+
17
+ nextTime = @timer.nextTime
18
+ @counter.prepare nextTime, nextTime / 1000, time
19
+
20
+ @currentCount = @counter.count
21
+ @currentTime = nextTime
22
+ @iterator = 0
23
+
24
+ hasNext: -> @iterator < @currentCount
25
+
26
+ next: ->
27
+ particle = @particleType.get position: @emitter.get()
28
+ @initializer?.initialize particle
29
+ @iterator++
30
+
31
+ particle
32
+
33
+ nextTime: -> @currentTime - @iterator / @currentCount * @currentTime
34
+
35
+ finished: -> @timer.finished()
@@ -0,0 +1,7 @@
1
+ namespace('agt.particles.emitters')
2
+ # Public:
3
+ class agt.particles.emitters.NullEmitter
4
+
5
+ ### Public ###
6
+
7
+ get: -> new agt.geom.Point
@@ -0,0 +1,10 @@
1
+ namespace('agt.particles.emitters')
2
+ # Public:
3
+ class agt.particles.emitters.Path
4
+ @include agt.particles.Randomizable
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@path, @random) -> @initRandom()
9
+
10
+ get: -> @path.pathPointAt @random.get()
@@ -0,0 +1,9 @@
1
+ namespace('agt.particles.emitters')
2
+ # Public:
3
+ class agt.particles.emitters.Ponctual
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@point=new agt.geom.Point) ->
8
+
9
+ get: -> @point.clone()
@@ -0,0 +1,10 @@
1
+ namespace('agt.particles.emitters')
2
+ # Public:
3
+ class agt.particles.emitters.Surface
4
+ @include agt.particles.Randomizable
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@surface, @random) -> @initRandom()
9
+
10
+ get: -> @surface.randomPointInSurface @random
@@ -0,0 +1,19 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.Explosion
4
+ @include agt.particles.Randomizable
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@velocityMin=0,
9
+ @velocityMax=1,
10
+ @angleMin=0,
11
+ @angleMax=Math.PI*2,
12
+ @random) -> @initRandom()
13
+
14
+ initialize: (particle) ->
15
+ angle = @random.in @angleMin, @angleMax
16
+ velocity = @random.in @velocityMin, @velocityMax
17
+
18
+ particle.velocity.x = Math.cos(angle)*velocity
19
+ particle.velocity.y = Math.sin(angle)*velocity
@@ -0,0 +1,16 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.Life
4
+ @include agt.particles.Randomizable
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@lifeMin, @lifeMax, @random) ->
9
+ @lifeMax = @lifeMin unless @lifeMax?
10
+ @initRandom()
11
+
12
+ initialize: (particle) ->
13
+ if @lifeMin is @lifeMax
14
+ particle.maxLife = @lifeMin
15
+ else
16
+ particle.maxLife = @random.in @lifeMin, @lifeMax
@@ -0,0 +1,10 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.MacroInitializer
4
+
5
+ ### Public ###
6
+
7
+ constructor: (@initializers) ->
8
+
9
+ initialize: (particle) ->
10
+ initializer.initialize particle for initializer in @initializers
@@ -0,0 +1,7 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.NullInitializer
4
+
5
+ ### Public ###
6
+
7
+ initialize: ->
@@ -0,0 +1,12 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.ParticleSubSystem
4
+
5
+ ### Public ###
6
+
7
+ constructor: (initializer, action, emissionFactory, subSystem) ->
8
+ @subSystem = new agt.particles.SubSystem(
9
+ initializer, action, emissionFactory, subSystem
10
+ )
11
+
12
+ initialize: (particle) -> @subSystem.emitFor particle
@@ -0,0 +1,20 @@
1
+ namespace('agt.particles.initializers')
2
+ # Public:
3
+ class agt.particles.initializers.Stream
4
+ @include agt.particles.Randomizable
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@direction=new agt.geom.Point(1,1),
9
+ @velocityMin=0,
10
+ @velocityMax=1,
11
+ @angleRandom=0,
12
+ @random) -> @initRandom()
13
+
14
+ initialize: (particle) ->
15
+ velocity = @random.in @velocityMin, @velocityMax
16
+ angle = @direction.angle()
17
+ angle += @random.pad @angleRandom if @angleRandom isnt 0
18
+
19
+ particle.velocity.x = Math.cos(angle) * velocity
20
+ particle.velocity.y = Math.sin(angle) * velocity
@@ -0,0 +1,10 @@
1
+ namespace('agt.particles')
2
+
3
+ DEFAULT_RANDOM = new agt.random.Random new agt.random.MathRandom
4
+
5
+ # Public:
6
+ class agt.particles.Randomizable
7
+
8
+ ### Public ###
9
+
10
+ initRandom: -> @random ||= DEFAULT_RANDOM
@@ -0,0 +1,32 @@
1
+ namespace('agt.particles')
2
+
3
+ # Public:
4
+ class agt.particles.Particle
5
+ @concern agt.mixins.Poolable
6
+
7
+ ### Public ###
8
+
9
+ init: (options={}) ->
10
+ @dead = false
11
+ @life = 0
12
+ @maxLife = 0
13
+ @position = new agt.geom.Point
14
+ @lastPosition = new agt.geom.Point
15
+ @velocity = new agt.geom.Point
16
+ @parasite = {}
17
+
18
+ @[ k ] = v for k,v of options
19
+
20
+ dispose: ->
21
+ @position = null
22
+ @lastPosition = null
23
+ @velocity = null
24
+ @parasite = null
25
+
26
+ die: ->
27
+ @dead = true
28
+ @life = @maxLife
29
+
30
+ revive: ->
31
+ @dead = false
32
+ @life = 0
@@ -0,0 +1,11 @@
1
+ namespace('agt.particles')
2
+
3
+ # Public:
4
+ class agt.particles.SubSystem extends agt.particles.System
5
+
6
+ ### Public ###
7
+
8
+ constructor: (initializer, action, @emissionFactory, subSystem) ->
9
+ super initializer, action, subSystem
10
+
11
+ emitFor: (particle) -> @emit @emissionFactory particle
@@ -0,0 +1,103 @@
1
+ namespace('agt.particles')
2
+
3
+ # Public:
4
+ class agt.particles.System
5
+
6
+ ### Public ###
7
+
8
+ constructor: (@initializer=new agt.particles.initializers.NullInitializer,
9
+ @action= new agt.particles.actions.NullAction, @subSystem) ->
10
+ @particlesCreated = new agt.Signal
11
+ @particlesDied = new agt.Signal
12
+ @emissionStarted = new agt.Signal
13
+ @emissionFinished = new agt.Signal
14
+ @particles = []
15
+ @emissions = []
16
+
17
+ emit: (emission) ->
18
+ @emissions.push emission
19
+ emission.system = this
20
+ @startEmission emission
21
+
22
+ startEmission: (emission) ->
23
+ emission.prepare 0, 0, @getTime()
24
+ @created = []
25
+ @died = []
26
+
27
+ @start() unless @running
28
+ @processEmission emission
29
+
30
+ @emissionStarted.dispatch this, emission
31
+ @particlesCreated.dispatch this, @created if @created.length > 0
32
+ @particlesDied.dispatch this, @died if @died.length > 0
33
+
34
+ @died = null
35
+ @created = null
36
+
37
+ start: ->
38
+ unless @running
39
+ agt.Impulse.instance().add @tick, this
40
+ @running = true
41
+
42
+ stop: ->
43
+ if @running
44
+ agt.Impulse.instance().remove @tick, this
45
+ @running = false
46
+
47
+ tick: (bias, biasInSeconds, time) ->
48
+ @died = []
49
+ @created = []
50
+
51
+ @processParticles bias, biasInSeconds, time
52
+ @processEmissions bias, biasInSeconds, time if @emitting()
53
+
54
+ @particlesCreated.dispatch this, @created if @created.length > 0
55
+ @particlesDied.dispatch this, @died if @died.length > 0
56
+
57
+ @died = null
58
+ @created = null
59
+
60
+ emitting: -> @emissions.length > 0
61
+
62
+ processEmissions: (bias, biasInSeconds, time) ->
63
+ for emission in @emissions.concat()
64
+ emission.prepare bias, biasInSeconds, time
65
+ @processEmission emission
66
+
67
+ processEmission: (emission) ->
68
+ while emission.hasNext()
69
+ time = emission.nextTime()
70
+ particle = emission.next()
71
+ @created.push particle
72
+ @registerParticle particle
73
+ @initializeParticle particle, time
74
+ if emission.finished()
75
+ @removeEmission emission
76
+ @emissionFinished.dispatch this, emission
77
+
78
+ removeEmission: (emission) ->
79
+ @emissions.splice @emissions.indexOf(emission), 1
80
+
81
+ processParticles: (bias, biasInSeconds, time) ->
82
+ @action.prepare bias, biasInSeconds, time
83
+ for particle in @particles.concat()
84
+ @action.process particle
85
+ @unregisterParticle particle if particle.dead
86
+
87
+ initializeParticle: (particle, time) ->
88
+ @initializer.initialize particle
89
+ @action.prepare time, time / 1000, @getTime()
90
+ @action.process particle
91
+
92
+ @unregisterParticle particle if particle.dead
93
+
94
+ registerParticle: (particle) ->
95
+ @particles.push particle
96
+
97
+ unregisterParticle: (particle) ->
98
+ @died.push particle
99
+ @subSystem?.emitFor particle
100
+ @particles.splice @particles.indexOf(particle), 1
101
+ particle.constructor.release particle
102
+
103
+ getTime: -> new Date().valueOf()