From a1d3b9705f2f3b0518bcee294a66882b9970257b Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Sat, 17 Oct 2026 23:23:03 -0700 Subject: [PATCH 1/5] [eve7] Fix two server crashes - An element with a transformation but no render data, e.g. a group, crashed the server. BuildRenderData() now sets the matrix only when there is render data. - A projected line with no points crashed the server. REveLineProjected::SetDepthLocal() now loops over the points by index. Assisted-by: Claude:Opus 5.5 --- graf3d/eve7/src/REveElement.cxx | 3 +-- graf3d/eve7/src/REveLine.cxx | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/graf3d/eve7/src/REveElement.cxx b/graf3d/eve7/src/REveElement.cxx index a86e5fcf1e279..9304f3cde2656 100644 --- a/graf3d/eve7/src/REveElement.cxx +++ b/graf3d/eve7/src/REveElement.cxx @@ -1483,8 +1483,7 @@ void REveElement::AddStamp(UChar_t bits) void REveElement::BuildRenderData() { - if (fMainTrans.get()) - { + if (fMainTrans && fRenderData) { fRenderData->SetMatrix(fMainTrans->Array()); } } diff --git a/graf3d/eve7/src/REveLine.cxx b/graf3d/eve7/src/REveLine.cxx index 33fe781aaa077..4e711e47fe950 100644 --- a/graf3d/eve7/src/REveLine.cxx +++ b/graf3d/eve7/src/REveLine.cxx @@ -354,10 +354,8 @@ void REveLineProjected::SetDepthLocal(Float_t d) { SetDepthCommon(d, this, fBBox); - Int_t n = fSize; - Float_t *p = & fPoints[0].fZ; - for (Int_t i = 0; i < n; ++i, p+=3) - *p = fDepth; + for (Int_t i = 0; i < fSize; ++i) + fPoints[i].fZ = fDepth; } //////////////////////////////////////////////////////////////////////////////// From 234f3a0b1304863e12d766fbec58310719586839 Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Sat, 17 Oct 2026 23:23:04 -0700 Subject: [PATCH 2/5] [eve7] Apply the matrix of a change that also carries a colour change - A change with both a new colour and a new matrix lost the matrix. EveManager.ImportSceneChangeJson() now handles both. Assisted-by: Claude:Opus 5.5 --- ui5/eve7/lib/EveManager.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ui5/eve7/lib/EveManager.js b/ui5/eve7/lib/EveManager.js index 8814e0c23d5ee..8666976a862c5 100644 --- a/ui5/eve7/lib/EveManager.js +++ b/ui5/eve7/lib/EveManager.js @@ -450,17 +450,19 @@ sap.ui.define([], function() { jQuery.extend(obj, em); this.ParseUpdateTriggersAndProcessPostStream(obj); } - else if (em.changeBit & this.EChangeBits.kCBColorSelection) { - delete em.render_data; - Object.assign(obj, em); - } - else if (em.changeBit & this.EChangeBits.kCBTransBBox) { - // Transformation-only update: no render data is streamed. - // render_data.matrix is updated here because a later rebuild, - // e.g. on a colour change, reads it. - Object.assign(obj, em); - if (em.matrix && obj.render_data) - obj.render_data.matrix = em.matrix; + else { + if (em.changeBit & this.EChangeBits.kCBColorSelection) { + delete em.render_data; + Object.assign(obj, em); + } + if (em.changeBit & this.EChangeBits.kCBTransBBox) { + // Transformation update: no render data is streamed. + // render_data.matrix is updated here because a later rebuild, + // e.g. on a colour change in the same message, reads it. + Object.assign(obj, em); + if (em.matrix && obj.render_data) + obj.render_data.matrix = em.matrix; + } } this.callSceneElementChange(scene, em); From db28466839c83e90d5f203c2ac759c5a8fa6f526 Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Sat, 17 Oct 2026 23:23:04 -0700 Subject: [PATCH 3/5] [eve7] Fix in-place point projection and the projection_axes.C managers - REveProjection::ProjectPointdv() gave wrong points when the input and the output were the same array. It now multiplies through a temporary. Added a gtest. - projection_axes.C now puts each projection manager in its projected scene. In EveWorld the client did not get the manager's updates. Assisted-by: Claude:Opus 5.5 --- graf3d/eve7/src/REveProjections.cxx | 9 ++++++--- graf3d/eve7/test/projection.cxx | 17 +++++++++++++++++ tutorials/visualisation/eve7/projection_axes.C | 8 ++++---- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/graf3d/eve7/src/REveProjections.cxx b/graf3d/eve7/src/REveProjections.cxx index 885b037a51fcf..4ea273147b3ea 100644 --- a/graf3d/eve7/src/REveProjections.cxx +++ b/graf3d/eve7/src/REveProjections.cxx @@ -93,7 +93,7 @@ void REveProjection::ProjectPointfv(const REveTrans* t, const Float_t* p, Float_ //////////////////////////////////////////////////////////////////////////////// /// Project double array, converting it to global coordinate system first if -/// transformation matrix is set. +/// transformation matrix is set. `p` and `v` may be the same array. /// This is a bit piggish as we convert the doubles to floats and back. void REveProjection::ProjectPointdv(const REveTrans* t, const Double_t* p, Double_t* v, Float_t d) @@ -101,8 +101,11 @@ void REveProjection::ProjectPointdv(const REveTrans* t, const Double_t* p, Doubl Float_t x, y, z; if (t) { - t->Multiply(p, v); - x = v[0]; y = v[1]; z = v[2]; + Double_t buf[3]; + t->Multiply(p, buf); + x = buf[0]; + y = buf[1]; + z = buf[2]; } else { diff --git a/graf3d/eve7/test/projection.cxx b/graf3d/eve7/test/projection.cxx index 707195eb2fdbb..644f797d1741a 100644 --- a/graf3d/eve7/test/projection.cxx +++ b/graf3d/eve7/test/projection.cxx @@ -16,6 +16,7 @@ #include #include #include +#include namespace REX = ROOT::Experimental; @@ -81,6 +82,22 @@ TEST(REveProjection, ScreenValRoundTrip) } } +// ProjectPointdv() may project in place, as REveTrackProjected does for path +// marks; a rotation must not read coordinates it has already overwritten. +TEST(REveProjection, ProjectPointdvInPlace) +{ + REX::REveRPhiProjection p; + REX::REveTrans t; + t.RotateLF(1, 2, 0.5); + t.SetPos(10, 20, 0); + + Double_t in[3] = {3, 4, 0}, out[3]; + p.ProjectPointdv(&t, in, out, 0.f); + p.ProjectPointdv(&t, in, in, 0.f); + for (int i = 0; i < 3; ++i) + EXPECT_DOUBLE_EQ(in[i], out[i]) << "coordinate " << i; +} + TEST(REveProjection, ImportAndUpdate) { auto eve = REX::REveManager::Create(); diff --git a/tutorials/visualisation/eve7/projection_axes.C b/tutorials/visualisation/eve7/projection_axes.C index ff198244fe1ef..54b017d926db4 100644 --- a/tutorials/visualisation/eve7/projection_axes.C +++ b/tutorials/visualisation/eve7/projection_axes.C @@ -109,10 +109,10 @@ static void makeProjectedView(REveManager *eveMng, REveElement *content, REvePro mng = new REveProjectionManager(type); mng->ImportElements(content, scene); - // Add the manager to the element tree. This gives it an element id, which the - // overlay buttons need as their MIR target, and streams its name, which shows - // the distortion, e.g. "RhoPhi (5.0)". - eveMng->GetWorld()->AddElement(mng); + // Add the manager to the scene it fills. This gives it an element id, which + // the overlay buttons need as their MIR target, and streams its name, which + // shows the distortion, e.g. "RhoPhi (5.0)". + scene->AddElement(mng); auto ovl = eveMng->SpawnNewScene(Form("%s Axis", name), name); ovl->SetIsOverlay(true); From 6f83ca5e00adc1f09873f5ad305810b3de9bf958 Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Sat, 17 Oct 2026 23:23:04 -0700 Subject: [PATCH 4/5] [eve7] Fit the camera to the scene's bounding sphere - The camera could start inside the scene: its distance assumed a 30 degree field of view, and the camera has 75. The distance now follows the camera's field of view. - The first camera placement happens before the scenes exist. The camera is now placed again when the first content arrives. Assisted-by: Claude:Opus 5.5 --- ui5/eve7/lib/GlViewerRCore.js | 14 +++++++++++--- ui5/eve7/lib/REveCameraControls.js | 8 +++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ui5/eve7/lib/GlViewerRCore.js b/ui5/eve7/lib/GlViewerRCore.js index 651fd3147bcdc..b6391822f238d 100644 --- a/ui5/eve7/lib/GlViewerRCore.js +++ b/ui5/eve7/lib/GlViewerRCore.js @@ -583,6 +583,8 @@ sap.ui.define([ setTimeout(this.syncCamTransTimer.bind(this), 5000); } + /** Set scene_bbox from the scene. An empty scene gets a fallback box of + * +-100. Returns true when the scene was empty. */ recalcSceneBBox() { // Exclude the axis: it is built from this box, and its ticks reach past @@ -592,7 +594,8 @@ sap.ui.define([ if (ax) this.scene.remove(ax); this.scene_bbox.setFromObject( this.scene ); if (ax) this.scene.add(ax); - if (this.scene_bbox.isEmpty()) + const empty = this.scene_bbox.isEmpty(); + if (empty) { console.error("GlViewerRenderCore.positionCameraAndLights scene bbox empty", this.scene_bbox); const ext = 100; @@ -602,6 +605,7 @@ sap.ui.define([ if (this.axis3d) this.axis3d.setBBox(this.axes_bbox || this.scene_bbox); this.updateRenderBBox(); + return empty; } /** The box the camera's near and far planes are fitted to: axes_bbox or @@ -619,7 +623,9 @@ sap.ui.define([ positionCameraAndLights() { - this.recalcSceneBBox(); + // An empty scene leaves the camera framing the fallback box; render() + // fits it again when content arrives. + this.camera_needs_fit = this.recalcSceneBBox(); let eveView = this.controller.mgr.GetElement(this.controller.eveViewerId); @@ -844,8 +850,10 @@ sap.ui.define([ if (this.annotations) this.annotations.layout(); if (this.axis3d) this.axis3d.updateForCamera(this.camera); if (this.render_requested_recalc_sbbox) { - this.recalcSceneBBox(); + const empty = this.recalcSceneBBox(); this.render_requested_recalc_sbbox = false; + if (this.camera_needs_fit && !empty) + this.positionCameraAndLights(); } if (this.camera.isPerspectiveCamera) { this.camera.optimizeNearFar(this.render_bbox || this.scene_bbox); diff --git a/ui5/eve7/lib/REveCameraControls.js b/ui5/eve7/lib/REveCameraControls.js index 8bd70172da028..c75b96edb0cc7 100644 --- a/ui5/eve7/lib/REveCameraControls.js +++ b/ui5/eve7/lib/REveCameraControls.js @@ -308,9 +308,11 @@ export class REveCameraControls extends EventDispatcher { camTrans.setPosition(localCenter); if (this.object.isPerspectiveCamera) { - const fovDefault = 30; - const fov = Math.min(fovDefault, this.object.aspect * fovDefault); - const dollyDefault = bb_R / (2.0 * Math.tan(fov * Math.PI * 0.8 / 180)); + // Far enough for the box's bounding sphere to fit the narrower of the + // vertical and the horizontal field of view. + const vHalf = 0.5 * this.object.fov * Math.PI / 180; + const hHalf = Math.atan(Math.tan(vHalf) * this.object.aspect); + const dollyDefault = bb_R / Math.sin(Math.min(vHalf, hHalf)); camTrans.moveLF(1, dollyDefault); } else { From 1c51de606b4720b24fe3a76b22eb2cc037f74124 Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Sat, 17 Oct 2026 23:23:04 -0700 Subject: [PATCH 5/5] [eve7] Deliver every motion update - Motion updates were lost when a connection could not take a direct send. They now go out with the regular changes. - Removed the motion rate cap, REveViewer::SetMotionMaxHz(). It dropped updates over the rate. SetRenderMaxHz() still caps drawing. Assisted-by: Claude:Opus 5.5 --- graf3d/eve7/inc/ROOT/REveViewer.hxx | 9 +---- graf3d/eve7/src/REveManager.cxx | 28 ++++++++++------ graf3d/eve7/src/REveViewer.cxx | 10 ------ ui5/eve7/controller/Ged.controller.js | 5 --- ui5/eve7/lib/EveManager.js | 4 --- ui5/eve7/lib/EveScene.js | 8 ++--- ui5/eve7/lib/GlViewerRCore.js | 2 -- ui5/eve7/lib/Motion.js | 48 +-------------------------- 8 files changed, 22 insertions(+), 92 deletions(-) diff --git a/graf3d/eve7/inc/ROOT/REveViewer.hxx b/graf3d/eve7/inc/ROOT/REveViewer.hxx index aa8e43a4217a4..2fe8f7d9a6227 100644 --- a/graf3d/eve7/inc/ROOT/REveViewer.hxx +++ b/graf3d/eve7/inc/ROOT/REveViewer.hxx @@ -88,7 +88,6 @@ private: Bool_t fHasAxesBBox{kFALSE}; ///< see SetAxesBBox() Float_t fAxesBBox[6]{}; ///< xmin, ymin, zmin, xmax, ymax, zmax - Float_t fMotionMaxHz{60.f}; ///< see SetMotionMaxHz(); 0 freezes Float_t fRenderMaxHz{0.f}; ///< see SetRenderMaxHz(); 0 is uncapped bool fBlackBackground{false}; @@ -161,14 +160,8 @@ public: void ClearAxesBBox(); Bool_t HasAxesBBox() const { return fHasAxesBBox; } - /// Cap on how often this viewer applies streamed motion, in updates per - /// second, clamped to [0, 240]. Zero freezes motion; SetExtrapolateMotion() - /// only controls drawing between updates. - Float_t GetMotionMaxHz() const { return fMotionMaxHz; } - void SetMotionMaxHz(Float_t); - /// Cap on how often the animation loop redraws this viewer, in frames per - /// second, clamped to [0, 240]. Zero means uncapped, unlike SetMotionMaxHz(). + /// second, clamped to [0, 240]. Zero means uncapped. Float_t GetRenderMaxHz() const { return fRenderMaxHz; } void SetRenderMaxHz(Float_t); // clang-format on diff --git a/graf3d/eve7/src/REveManager.cxx b/graf3d/eve7/src/REveManager.cxx index 913959309f8b3..81029a0ec1cb0 100644 --- a/graf3d/eve7/src/REveManager.cxx +++ b/graf3d/eve7/src/REveManager.cxx @@ -1151,13 +1151,22 @@ void REveManager::StreamSceneChangesToJson() //////////////////////////////////////////////////////////////////////////////// /// Send transformation-only changes on the motion channel: the same websocket, -/// outside the BeginChanges/EndChanges cycle, with no acknowledgement. A -/// connection for which RWebWindow::CanSend(id, true) is false skips the -/// message. Each message is absolute state, but a client that skips the last -/// one keeps the old state until the element changes again. +/// outside the BeginChanges/EndChanges cycle, with no acknowledgement. The +/// message is sent directly, so it goes only when every connection can take a +/// direct send. Otherwise the elements stay stamped and go out with the +/// regular changes, which carry the same matrix and motion. The motion's t0 is +/// stamped by REveTrans::SetMotion(), so the delay does not shift the +/// trajectory. void REveManager::SendMotionChanges() { + if (fConnList.empty()) + return; + + for (auto &conn : fConnList) + if (!fWebWindow->CanSend(conn.fId, true)) + return; + nlohmann::json arr = nlohmann::json::array(); fWorld->StreamMotionChanges(arr); @@ -1167,21 +1176,19 @@ void REveManager::SendMotionChanges() s->StreamMotionChanges(arr); } - if (arr.empty() || fConnList.empty()) + if (arr.empty()) return; nlohmann::json msg = {}; // clang-format off msg["content"] = "Motion"; - msg["t"] = REveUtil::ServerTimeMs(); msg["els"] = arr; // clang-format on std::string data = msg.dump(); for (auto &conn : fConnList) - if (fWebWindow->CanSend(conn.fId, true)) - fWebWindow->Send(conn.fId, data); + fWebWindow->Send(conn.fId, data); } //////////////////////////////////////////////////////////////////////////////// @@ -1414,7 +1421,8 @@ void REveManager::BeginChange() /// Stop accepting changes and stream them. If the clients have not yet /// acknowledged the previous changes, keep the stamps and let the last /// acknowledgement in WindowData() flush them. Stamps coalesce per element. -/// Motion changes are sent in either case. Changes made by a MIR are not held +/// Motion changes are sent in either case when the connections can take a +/// direct send, see SendMotionChanges(). Changes made by a MIR are not held /// back. void REveManager::EndChange() @@ -1426,7 +1434,7 @@ void REveManager::EndChange() std::unique_lock lock(fServerState.fMutex); // Motion is outside the change cycle, so it is sent even when changes - // are held. + // are held. What cannot be sent now stays stamped and goes with them. SendMotionChanges(); if (!fConnList.empty() && !ClientConnectionsFree()) { diff --git a/graf3d/eve7/src/REveViewer.cxx b/graf3d/eve7/src/REveViewer.cxx index abcc31b22432e..1e4c27c4675eb 100644 --- a/graf3d/eve7/src/REveViewer.cxx +++ b/graf3d/eve7/src/REveViewer.cxx @@ -151,15 +151,6 @@ void REveViewer::SetRenderMaxHz(Float_t hz) StampObjProps(); } -//////////////////////////////////////////////////////////////////////////////// -/// Cap the rate at which this viewer applies streamed motion; 0 freezes it. - -void REveViewer::SetMotionMaxHz(Float_t hz) -{ - fMotionMaxHz = hz < 0.f ? 0.f : (hz > 240.f ? 240.f : hz); - StampObjProps(); -} - //////////////////////////////////////////////////////////////////////////////// /// Set the point the camera looks at and orbits around, either the world /// origin or the center of the scene bounding box. Clients reset their camera @@ -290,7 +281,6 @@ int REveViewer::WriteCoreJson(nlohmann::json &j, Int_t rnr_offset) j["CameraCenter"] = fCameraCenter; j["ExtrapolateMotion"] = fExtrapolateMotion; j["AxesUpAxis"] = fAxesUpAxis; - j["MotionMaxHz"] = fMotionMaxHz; j["RenderMaxHz"] = fRenderMaxHz; // clang-format off if (fHasAxesBBox) diff --git a/ui5/eve7/controller/Ged.controller.js b/ui5/eve7/controller/Ged.controller.js index 051a8f860ae78..33137f164f5fb 100644 --- a/ui5/eve7/controller/Ged.controller.js +++ b/ui5/eve7/controller/Ged.controller.js @@ -400,7 +400,6 @@ sap.ui.define([ // Motion settings, in a collapsed panel since they matter only while // something moves: // - // MotionMaxHz how often the update stream is applied // RenderMaxHz how often the result is drawn // ExtrapolateMotion whether anything is drawn between updates let motion = new sap.m.Panel({ @@ -411,10 +410,6 @@ sap.ui.define([ }); this.getView().byId("GED").addContent(motion); - this.makeSliderSetter(el.MotionMaxHz, "MotionMaxHz", null, - {min: 0, max: 60, step: 1, - tip: "Updates per second this viewer applies. 0 freezes the scene."}, - motion); this.makeSliderSetter(el.RenderMaxHz, "RenderMaxHz", null, {min: 0, max: 120, step: 1, tip: "Animation redraws per second. 0 is uncapped -- every display frame."}, diff --git a/ui5/eve7/lib/EveManager.js b/ui5/eve7/lib/EveManager.js index 8666976a862c5..2e416e603f54e 100644 --- a/ui5/eve7/lib/EveManager.js +++ b/ui5/eve7/lib/EveManager.js @@ -813,10 +813,6 @@ sap.ui.define([], function() { let obj = this.map[em.fElementId]; if (!obj) continue; // removed, or not yet created - // The message's own timestamp, so a viewer's rate cap can decide - // once for the whole message rather than once per element. - em.msg_t = resp.t; - // Keep the model in step: a later rebuild reads render_data.matrix, // and a stale one would put the object back where it used to be. if (em.matrix && obj.render_data) diff --git a/ui5/eve7/lib/EveScene.js b/ui5/eve7/lib/EveScene.js index 4fbcaed15e1b1..d608ce0b90ef3 100644 --- a/ui5/eve7/lib/EveScene.js +++ b/ui5/eve7/lib/EveScene.js @@ -263,18 +263,14 @@ sap.ui.define(['rootui5/eve7/lib/EveManager'], function(EveManager) { let el = this.mgr.GetElement(msg.fElementId); if (!el) return; - // The viewer's rate cap. Dropping an update is safe because each - // carries absolute state. - let mo = this.glctrl ? this.glctrl.viewer.motion : null; - if (mo && !mo.acceptUpdate(msg.msg_t)) return; - this.updateElementTrans(el, msg); // No endChanges() follows, so request the render here. Going through // Motion applies the viewer's render cap. + let mo = this.glctrl.viewer.motion; if (mo) mo.requestRender(); - else if (this.glctrl && this.glctrl.viewer) + else this.glctrl.viewer.request_render(); } diff --git a/ui5/eve7/lib/GlViewerRCore.js b/ui5/eve7/lib/GlViewerRCore.js index b6391822f238d..581b0faca2d92 100644 --- a/ui5/eve7/lib/GlViewerRCore.js +++ b/ui5/eve7/lib/GlViewerRCore.js @@ -766,8 +766,6 @@ sap.ui.define([ this.axis3d.setBBox(this.axes_bbox || this.scene_bbox); this.updateRenderBBox(); - if (eveView.MotionMaxHz !== undefined && this.motion) - this.motion.setMaxHz(eveView.MotionMaxHz); if (eveView.RenderMaxHz !== undefined && this.motion) this.motion.setRenderMaxHz(eveView.RenderMaxHz); if (eveView.ExtrapolateMotion !== undefined && this.motion) diff --git a/ui5/eve7/lib/Motion.js b/ui5/eve7/lib/Motion.js index fb9717a36877c..499e6010e989a 100644 --- a/ui5/eve7/lib/Motion.js +++ b/ui5/eve7/lib/Motion.js @@ -23,36 +23,12 @@ sap.ui.define([], function() { this.raf = 0; this.enabled = true; - /** Cap on applied updates per second; 0 freezes. See setMaxHz(). */ - this.max_hz = 60; - this._last_app = 0; - /** Server timestamp of the message the last accept/drop decision was - * made for, and that decision. Every element of one message shares it. */ - this._msg_t = null; - this._msg_ok = false; - /** Cap on animation-driven redraws per second; 0 is uncapped. */ this.render_max_hz = 0; this._last_render = 0; this._pending = 0; } - /** How often this viewer may apply streamed motion, from - * REveViewer::SetMotionMaxHz. A negative value means 60. - * - * Zero freezes the viewer: updates are dropped and extrapolation stops. - * setEnabled(false) differs: it stops only the extrapolation, and - * objects still step at the server's update rate. - */ - setMaxHz(hz) { - hz = (hz >= 0) ? hz : 60; - if (hz === this.max_hz) return; - this.max_hz = hz; - - if (hz === 0) this.stop(); - else this.start(); - } - /** How often the animation loop may redraw, from * REveViewer::SetRenderMaxHz. Zero is uncapped. Positions are evaluated * at draw time, so a capped viewer is coarser but not behind. @@ -90,28 +66,6 @@ sap.ui.define([], function() { } } - /** Gate for an incoming motion update: false means drop it. - * - * `t` is the server timestamp of the whole message (msg_t, set in - * EveManager). The decision is made once per message and reused for - * every element in it. Deciding per element would let the first element - * spend the budget and refuse the rest every time. - */ - acceptUpdate(t) { - if (this.max_hz === 0) return false; - - if (t !== undefined && t === this._msg_t) - return this._msg_ok; - - const now = performance.now(); - const ok = (now - this._last_app) >= 1000 / this.max_hz; - if (ok) this._last_app = now; - - this._msg_t = t; - this._msg_ok = ok; - return ok; - } - /** Per-viewer switch for extrapolation, from * REveViewer::SetExtrapolateMotion. * @@ -211,7 +165,7 @@ sap.ui.define([], function() { //-------------------------------------------------------------------- start() { - if (this.raf || this.objects.size === 0 || !this.enabled || this.max_hz === 0) return; + if (this.raf || this.objects.size === 0 || !this.enabled) return; this.raf = requestAnimationFrame(this.tick.bind(this)); }