OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
movetrigger.cpp
Go to the documentation of this file.
1#include "movetrigger.h"
2
3#include <Tempest/Log>
4
6#include "game/serialize.h"
7#include "world/world.h"
8
9using namespace Tempest;
10
11MoveTrigger::MoveTrigger(Vob* parent, World& world, const zenkit::VMover& mover, Flags flags)
12 :AbstractTrigger(parent,world,mover,flags) {
13 moverKeyFrames = mover.keyframes;
14 behavior = mover.behavior;
15 speedType = mover.speed_mode;
16 lerpType = mover.lerp_mode;
17 autoRotate = mover.auto_rotate;
18 sfxOpenStart = mover.sfx_open_start;
19 stayOpenTime = uint64_t(mover.stay_open_time_sec * 1000.f);
20 sfxOpenEnd = mover.sfx_open_end;
21 sfxCloseStart = mover.sfx_close_start;
22 sfxCloseEnd = mover.sfx_close_end;
23 sfxMoving = mover.sfx_transitioning;
24 visualName = mover.visual->name;
25
26 if(mover.cd_dynamic || mover.cd_static) {
27 if(auto mesh = Resources::loadMesh(mover.visual->name))
28 physic = PhysicMesh(*mesh,*world.physic(),true);
29 }
30
31 const float speed = mover.speed;
32 keyframes.resize(moverKeyFrames.size());
33 for(size_t i=0; i<moverKeyFrames.size(); ++i) {
34 auto& f0 = moverKeyFrames[i];
35 auto& f1 = moverKeyFrames[(i+1)%moverKeyFrames.size()];
36 auto dx = (f1.position.x-f0.position.x);
37 auto dy = (f1.position.y-f0.position.y);
38 auto dz = (f1.position.z-f0.position.z);
39
40 float theta = 2.f*std::pow(f1.rotation.x*f0.rotation.x + f1.rotation.y*f0.rotation.y + f1.rotation.z*f0.rotation.z + f1.rotation.w*f0.rotation.w, 2.f) - 1.f;
41 float angle = float(std::acos(std::clamp(theta, -1.f, 1.f))*180.0/M_PI);
42 float len = Vec3(dx,dy,dz).length();
43
44 if(speed>0) {
45 /*
46 * Distance between keyframe positions doesn't have to be exactly zero to use rotation speed,
47 * it seem there is some small threshold.
48 * Otherwise the boat close to Lares in L'Hiver mod would jump between two keyframes.
49 */
50 static const float rotationThreshold = 0.01f;
51
52 uint64_t ticks = 0;
53 if(len>rotationThreshold)
54 ticks = uint64_t(len/speed); else
55 ticks = uint64_t((angle/360.f) * 1000.f/scaleRotSpeed(speed));
56 keyframes[i].ticks = std::max<uint64_t>(1,ticks);
57 }
58 }
59
60 auto tr = transform();
61 if(frame<moverKeyFrames.size())
62 tr = mkMatrix(moverKeyFrames[frame]);
63 tr.inverse();
64 pos0 = localTransform();
65 pos0.mul(tr);
66
67 invalidateView();
68 MoveTrigger::moveEvent();
69 }
70
71void MoveTrigger::save(Serialize& fout) const {
73 fout.write(pos0,uint8_t(state),frameTime,frame,targetFrame);
74 }
75
78 fin.read(pos0,reinterpret_cast<uint8_t&>(state),frameTime,frame);
79 if(fin.version()>49)
80 fin.read(targetFrame);
81 if(state!=Idle) {
82 invalidateView();
84 }
85 }
86
87float MoveTrigger::scaleRotSpeed(float speed) const {
88 // Measurements for rotation only movers show contradictory values without a clear path to translate speed attribute into actual movement speed.
89 // Assume speed is given as rotations/s and scale movers which would move extremely slow compared to vanilla.
90 // first column : time measured needed for one rotation
91 // second column: speed attribute
92 // third column : tells us how much faster this mover is compared to a mover that would take speed as rot/s.
93
94 // time speed 1/(time*speed)
95 // Newworld
96 // NW_MISC_WINDMILL_ROTOR_01 44.40 0.02 1.13
97 // NW_HARBOUR_BOAT_03 271.04 0.0005 7.38
98 // EVT_TROLL_GRAVE_MOVER_01 19.20 0.0001 520.83
99 // EVT_CITY_REICH03_01 4.48 0.005 44.67
100
101 // Irdorath
102 // EVT_RINGMAIN_LEFT_01 3.40 0.001 294.12
103 // EVT_RIGHT_WHEEL_01 5.00 0.00068 294.12
104 // EVT_RIGHT_WHEEL_02 5.00 0.2 1.00
105 // EVT_RIGHT_ROOM_01_SPAWN_ROT_02 2.8 0.001 357.14
106 // EVT_LEFT_ROOM_02_SPAWN_ROT_02 2.56 0.0005 781.25
107
108 // Addonworld Adanos temple door
109 // EVT_ADDON_LSTTEMP_DOOR_LEFT_01 16.66 0.00028 214.42
110 // EVT_ADDON_LSTTEMP_DOOR_RIGHT_01 16.97 0.2 0.29
111
112 // Use a general scaling factor and specialize for boats only where low speed is intended.
113 const float sFactor = 300;
114 const float sFactorBoat = 7;
115 const float lowSpeed = 0.006f;
116 if(world.name()=="newworld.zen" && vobName.starts_with("NW_HARBOUR_BOAT_"))
117 return sFactorBoat*speed;
118 if(speed<lowSpeed)
119 return sFactor*speed;
120 return speed;
121 }
122
123void MoveTrigger::setView(MeshObjects::Mesh &&m) {
124 view = std::move(m);
125 }
126
127void MoveTrigger::emitSound(std::string_view snd,bool freeSlot) const {
128 auto p = position();
129 auto sfx = ::Sound(world,::Sound::T_Regular,snd,p,0,freeSlot);
130 sfx.play();
131 }
132
133void MoveTrigger::invalidateView() {
134 if(isDynamic() && !view.isEmpty())
135 return;
136
137 if(state!=Idle || isDynamic())
138 setView(world.addView(visualName)); else
139 setView(world.addStaticView(visualName));
140 view.setObjMatrix(transform());
141 }
142
143void MoveTrigger::moveEvent() {
145 view .setObjMatrix(transform());
146 physic.setObjMatrix(transform());
147 }
148
150 if(moverKeyFrames.size()<2 || keyframes[0].ticks==0)
151 return;
152
153 auto prev = state;
154 switch(behavior) {
155 case zenkit::MoverBehavior::TOGGLE: {
156 if((frame==0 && state==Idle) || state==Close)
157 state = Open; else
158 state = Close;
159 break;
160 }
161 case zenkit::MoverBehavior::TRIGGER_CONTROL: {
162 if(frame==0 && state==Idle)
163 state = Open;
164 break;
165 }
166 case zenkit::MoverBehavior::OPEN_TIME: {
167 if(state==OpenTimed)
168 frameTime = 0; else
169 state = Open;
170 break;
171 }
172 case zenkit::MoverBehavior::LOOP: {
173 state = Loop;
174 break;
175 }
176 case zenkit::MoverBehavior::SINGLE_KEYS: {
177 return;
178 }
179 }
180 preProcessTrigger(prev);
181 }
182
184 if(keyframes.size()<2 || keyframes[0].ticks==0)
185 return;
186 if(behavior!=zenkit::MoverBehavior::TRIGGER_CONTROL)
187 return;
188 if(frame>0 && state==Idle) {
189 state = Close;
190 targetFrame = 0;
191 preProcessTrigger();
192 }
193 }
194
196 if(keyframes.size()<2 || keyframes[0].ticks==0)
197 return;
198 if(evt.move.key<0 || keyframes.size()<size_t(evt.move.key))
199 return;
200 if(behavior!=zenkit::MoverBehavior::SINGLE_KEYS)
201 return;
202 if(state!=Idle)
203 return;
204 state = SingleKey;
205 switch(evt.move.msg) {
206 case zenkit::MoverMessageType::NEXT:
207 targetFrame = nextFrame(frame);
208 break;
209 case zenkit::MoverMessageType::PREVIOUS:
210 targetFrame = prevFrame(frame);
211 break;
212 case zenkit::MoverMessageType::FIXED_DIRECT:
213 case zenkit::MoverMessageType::FIXED_ORDER:
214 targetFrame = uint32_t(evt.move.key);
215 break;
216 }
217 preProcessTrigger();
218 }
219
220void MoveTrigger::preProcessTrigger(State prev) {
221 if(prev==state || state==Idle)
222 return;
223 if(state==Open) {
224 targetFrame = uint32_t(keyframes.size())-1;
225 emitSound(sfxOpenStart);
226 if(prev==Close) {
227 frameTime = keyframes[frame].ticks - frameTime;
228 frame = nextFrame(frame);
229 return;
230 }
231 }
232 else if(state==Close) {
233 targetFrame = 0;
234 emitSound(sfxCloseStart);
235 if(prev==Open) {
236 frame = prevFrame(frame);
237 frameTime = keyframes[frame].ticks - frameTime;
238 return;
239 }
240 }
241 invalidateView();
242 enableTicks();
243 }
244
245void MoveTrigger::postProcessTrigger() {
246 if(!target.empty()) {
249 }
250 if(state==Open)
251 emitSound(sfxOpenEnd);
252 if(state==Close)
253 emitSound(sfxCloseEnd);
254 if(behavior==zenkit::MoverBehavior::OPEN_TIME && state==Open) {
255 state = OpenTimed;
256 return;
257 }
258 state = Idle;
259 invalidateView();
260 disableTicks();
261 }
262
263uint32_t MoveTrigger::nextFrame(uint32_t i) const {
264 uint32_t size = uint32_t(keyframes.size());
265 if(behavior==zenkit::MoverBehavior::LOOP)
266 return (i+1)%size;
267 return std::min<uint32_t>(i+1, size-1);
268 }
269
270uint32_t MoveTrigger::prevFrame(uint32_t i) const {
271 uint32_t size = uint32_t(keyframes.size());
272 if(behavior==zenkit::MoverBehavior::LOOP)
273 return (i+size-1)%size;
274 return std::max<uint32_t>(i, 1) - 1;
275 }
276
277void MoveTrigger::tick(uint64_t dt) {
278 assert(state!=Idle);
279 frameTime += dt;
280 if(state==OpenTimed) {
281 if(frameTime<=stayOpenTime)
282 return;
283 state = Close;
284 targetFrame = 0;
285 frameTime -= stayOpenTime;
286 }
287 if(frame==targetFrame) {
288 postProcessTrigger();
289 return;
290 }
291 advanceAnim();
292 updateFrame();
293 emitSound(sfxMoving);
294 }
295
296void MoveTrigger::updateFrame() {
297 uint32_t f = (state==Close) ? prevFrame(frame) : frame;
298 if(frameTime<keyframes[f].ticks)
299 return;
300 if(state==Close)
301 frame = prevFrame(frame); else
302 frame = nextFrame(frame);
303 frameTime = 0;
304 }
305
306 void MoveTrigger::advanceAnim() {
307 uint32_t f1 = (state==Close) ? prevFrame(frame) : frame;
308 uint32_t f2 = nextFrame(f1);
309 float a = calcProgress(f1,f2);
310 auto m = calcTransform(f1,f2,a);
311 auto mat = pos0;
312 mat.mul(m);
314 }
315
316float MoveTrigger::calcProgress(uint32_t f1, uint32_t f2) const {
317 float a = float(frameTime)/float(keyframes[f1].ticks);
318 a = std::clamp(a,0.f,1.f);
319 if(state==Close)
320 a = 1 - a;
321 bool start = (f1==0 && state!=Loop);
322 bool end = (f2==moverKeyFrames.size()-1 && state!=Loop);
323 switch(speedType) {
324 case zenkit::MoverSpeedType::CONSTANT:
325 break;
326 case zenkit::MoverSpeedType::SLOW_START_END:
327 case zenkit::MoverSpeedType::SEGMENT_SLOW_START_END:
328 if((start && end) || speedType==zenkit::MoverSpeedType::SEGMENT_SLOW_START_END)
329 a = (3 - 2*a) * a*a;
330 else if(start)
331 a = (2 - a) * a*a;
332 else if(end)
333 a = ((1 - a)*a + 1)*a;
334 break;
335 case zenkit::MoverSpeedType::SLOW_START:
336 case zenkit::MoverSpeedType::SEGMENT_SLOW_START:
337 if(start || speedType==zenkit::MoverSpeedType::SEGMENT_SLOW_START)
338 a = (2 - a) * a*a;
339 break;
340 case zenkit::MoverSpeedType::SLOW_END:
341 case zenkit::MoverSpeedType::SEGMENT_SLOW_END:
342 if(end || speedType==zenkit::MoverSpeedType::SEGMENT_SLOW_END)
343 a = ((1 - a)*a + 1)*a;
344 break;
345 }
346 return std::clamp(a,0.f,1.f);
347 }
348
349Matrix4x4 MoveTrigger::calcTransform(uint32_t f1, uint32_t f2, float a) const {
350 zenkit::AnimationSample fr = {};
351 Vec3 forward = {};
352 if(lerpType==zenkit::MoverLerpType::LINEAR) {
353 auto& pos1 = moverKeyFrames[f1].position;
354 auto& pos2 = moverKeyFrames[f2].position;
355 fr = mix(moverKeyFrames[f1],moverKeyFrames[f2],a);
356 forward = {pos2.x-pos1.x,pos2.y-pos1.y,pos2.z-pos1.z};
357 } else {
358 auto& kF0 = moverKeyFrames[prevFrame(f1)].position;
359 auto& kF1 = moverKeyFrames[f1].position;
360 auto& kF2 = moverKeyFrames[f2].position;
361 auto& kF3 = moverKeyFrames[nextFrame(f2)].position;
362 Vec3 p0 = {kF0.x,kF0.y,kF0.z};
363 Vec3 p1 = {kF1.x,kF1.y,kF1.z};
364 Vec3 p2 = {kF2.x,kF2.y,kF2.z};
365 Vec3 p3 = {kF3.x,kF3.y,kF3.z};
366 Vec3 b1 = -p0 + p1 - p2 + p3;
367 Vec3 b2 = 2*p0 - 2*p1 + p2 - p3;
368 Vec3 b3 = -p0 + p2;
369 Vec3 pos = ((b1*a + b2)*a + b3)*a + p1;
370 forward = (3*b1*a + 2*b2)*a + b3;
371 fr.position.x = pos.x;
372 fr.position.y = pos.y;
373 fr.position.z = pos.z;
374 fr.rotation = slerp(moverKeyFrames[f1].rotation,moverKeyFrames[f2].rotation,a);
375 }
376
377 if(!autoRotate)
378 return mkMatrix(fr);
379 Vec3 z = Vec3::normalize(forward);
380 Vec3 up = std::abs(z.y)<0.999f ? Vec3(0,1,0) : Vec3(1,0,0);
381 Vec3 x = Vec3::normalize(Vec3::crossProduct(up,z));
382 Vec3 y = Vec3::crossProduct(z,x);
383 return {
384 x.x, y.x, z.x, fr.position.x,
385 x.y, y.y, z.y, fr.position.y,
386 x.z, y.z, z.z, fr.position.z,
387 0, 0, 0, 1
388 };
389 }
static Tempest::Matrix4x4 mkMatrix(float x, float y, float z, float w, float px, float py, float pz)
Definition animmath.cpp:55
zenkit::Quat slerp(const zenkit::Quat &x, const zenkit::Quat &y, float a)
Definition animmath.cpp:9
void load(Serialize &fin) override
void save(Serialize &fout) const override
std::string vobName
void moveEvent() override
bool isEmpty() const
Definition meshobjects.h:62
void setObjMatrix(const Tempest::Matrix4x4 &mt)
void save(Serialize &fout) const override
void onTrigger(const TriggerEvent &evt) override
void onGotoMsg(const TriggerEvent &evt) override
MoveTrigger(Vob *parent, World &world, const zenkit::VMover &data, Flags flags)
void onUntrigger(const TriggerEvent &evt) override
void tick(uint64_t dt) override
void load(Serialize &fin) override
void setObjMatrix(const Tempest::Matrix4x4 &m)
static const ProtoMesh * loadMesh(std::string_view name)
void write(const Arg &... a)
Definition serialize.h:76
uint16_t version() const
Definition serialize.h:51
void read(Arg &... a)
Definition serialize.h:81
Definition sound.h:5
@ T_Regular
Definition sound.h:8
zenkit::MoverMessageType msg
struct TriggerEvent::@26 move
Definition vob.h:11
virtual bool isDynamic() const
Definition vob.cpp:88
Flags
Definition vob.h:13
World & world
Definition vob.h:45
void setLocalTransform(const Tempest::Matrix4x4 &p)
Definition vob.cpp:73
auto localTransform() const -> const Tempest::Matrix4x4 &
Definition vob.h:37
auto transform() const -> const Tempest::Matrix4x4 &
Definition vob.h:34
Tempest::Vec3 position() const
Definition vob.cpp:57
Definition world.h:31
void triggerEvent(const TriggerEvent &e)
Definition world.cpp:495
DynamicWorld * physic() const
Definition world.h:81
std::string_view name() const
Definition world.h:42
MeshObjects::Mesh addView(std::string_view visual) const
Definition world.cpp:251
MeshObjects::Mesh addStaticView(const ProtoMesh *visual, bool staticDraw)
Definition world.cpp:271
static float mix(float x, float y, float a)