-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy patheffectors.py
180 lines (149 loc) · 5.96 KB
/
effectors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#
# Copyright 2009-2012 Alex Fraser <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import bge
import mathutils
import bat.bats
import bat.utils
import bat.bmath
class ForceField(bat.bats.BX_GameObject, bge.types.KX_GameObject):
_prefix = 'FF_'
log = logging.getLogger(__name__ + ".ForceField")
def __init__(self, old_owner):
self.set_state(2)
def modulate(self, distance, limit):
'''
To visualise this function, try it in gnuplot:
f(d, l) = (d*d) / (l*l)
plot [0:10][0:1] f(x, 10)
'''
return (distance * distance) / (limit * limit)
def get_magnitude(self, distance):
effect = 0.0
if distance < self['FFDist1']:
effect = self.modulate(distance, self['FFDist1'])
else:
effect = 1.0 - self.modulate(distance - self['FFDist1'],
self['FFDist2'])
if effect > 1.0:
effect = 1.0
if effect < 0.0:
effect = 0.0
return self['FFMagnitude'] * effect
@bat.bats.expose
@bat.utils.controller_cls
def touched(self, c):
actors = set()
for s in c.sensors:
if not s.positive:
continue
for ob in s.hitObjectList:
actors.add(ob)
for a in actors:
self.touched_single(a)
def get_world_acceleration(self, actor):
pos = actor.worldPosition
dist = (pos - self.worldPosition).magnitude
if dist > self['FFDist2'] or dist < 0.0001:
return bat.bmath.ZEROVEC.copy()
pos = bat.bmath.to_local(self, pos)
if 'FFZCut' in self and self['FFZCut'] and (pos.z > 0.0):
return bat.bmath.ZEROVEC.copy()
vec = self.get_force_direction(pos)
vec.normalize()
magnitude = self.get_magnitude(dist)
ForceField.log.debug("Force magnitude of %s on %s is %g", self, actor, magnitude)
vec *= magnitude
return bat.bmath.to_world_vec(self, vec)
def touched_single(self, actor):
'''Called when an object is inside the force field.'''
accel = self.get_world_acceleration(actor)
actor.worldLinearVelocity = bat.bmath.integrate_v(
actor.worldLinearVelocity, accel, 0.0)
def get_force_direction(self, localPos):
'''Returns the Vector along which the acceleration will be applied, in
local space.'''
pass
class Linear(ForceField):
def __init__(self, old_owner):
ForceField.__init__(self, old_owner)
def get_force_direction(self, posLocal):
return bat.bmath.to_local_vec(self, self.getAxisVect(bat.bmath.YAXIS))
def modulate(self, distance, limit):
'''
To visualise this function, try it in gnuplot:
f(d, l) = d / l
plot [0:10][0:1] f(x, 10)
'''
return distance / limit
class Repeller3D(ForceField):
'''
Repels objects away from the force field's origin.
Object properties:
FFMagnitude: The maximum acceleration.
FFDist1: The distance from the origin at which the maximum acceleration will
be applied.
FFDist2: The distance from the origin at which the acceleration will be
zero.
FFZCut: If True, force will only be applied to objects underneath the force
field's XY plane (in force field local space).
'''
def __init__(self, old_owner):
ForceField.__init__(self, old_owner)
def get_force_direction(self, posLocal):
return posLocal
class Repeller2D(ForceField):
'''
Repels objects away from the force field's origin on the local XY axis.
Object properties:
FFMagnitude: The maximum acceleration.
FFDist1: The distance from the origin at which the maximum acceleration will
be applied.
FFDist2: The distance from the origin at which the acceleration will be
zero.
FFZCut: If True, force will only be applied to objects underneath the force
field's XY plane (in force field local space).
'''
def __init__(self, old_owner):
ForceField.__init__(self, old_owner)
def get_force_direction(self, posLocal):
vec = mathutils.Vector(posLocal)
vec.z = 0.0
return vec
class Vortex2D(ForceField):
'''
Propels objects around the force field's origin, so that the rotate around
the Z-axis. Rotation will be clockwise for positive magnitudes. Force is
applied tangentially to a circle around the Z-axis, so the objects will tend
to spiral out from the centre. The magnitude of the acceleration varies
depending on the distance of the object from the origin: at the centre, the
acceleration is zero. It ramps up slowly (r-squared) to the first distance
marker; then ramps down (1 - r-squared) to the second.
Object properties:
FFMagnitude: The maximum acceleration.
FFDist1: The distance from the origin at which the maximum acceleration will
be applied.
FFDist2: The distance from the origin at which the acceleration will be
zero.
FFZCut: If True, force will only be applied to objects underneath the force
field's XY plane (in force field local space).
'''
def __init__(self, old_owner):
ForceField.__init__(self, old_owner)
def get_force_direction(self, posLocal):
tan = mathutils.Vector((posLocal.y, 0.0 - posLocal.x, 0.0))
return tan