bool ScrollAnimatorWin::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier) { // Don't animate jumping to the beginning or end of the document. if (granularity == ScrollByDocument) return ScrollAnimator::scroll(orientation, granularity, step, multiplier); // This is an animatable scroll. Calculate the scroll delta. PerAxisData* data = (orientation == VerticalScrollbar) ? &m_verticalData : &m_horizontalData; float newPos = std::max(std::min(data->m_desiredPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0.0f); if (newPos == data->m_desiredPos) return false; data->m_desiredPos = newPos; // Calculate the animation velocity. if (*data->m_currentPos == data->m_desiredPos) return false; bool alreadyAnimating = data->m_animationTimer.isActive(); // There are a number of different sources of scroll requests. We want to // make both keyboard and wheel-generated scroll requests (which can come at // unpredictable rates) and autoscrolling from holding down the mouse button // on a scrollbar part (where the request rate can be obtained from the // scrollbar theme) feel smooth, responsive, and similar. // // When autoscrolling, the scrollbar's autoscroll timer will call us to // increment the desired position by |step| (with |multiplier| == 1) every // ScrollbarTheme::theme()->autoscrollTimerDelay() seconds. If we set // the desired velocity to exactly this rate, smooth scrolling will neither // race ahead (and then have to slow down) nor increasingly lag behind, but // will be smooth and synchronized. // // Note that because of the acceleration period, the current position in // this case would lag the desired one by a small, constant amount (see // comments on animateScroll()); the exact amount is given by // lag = |step| - v(0.5tA + tD) // Where // v = The steady-state velocity, // |step| / ScrollbarTheme::theme()->autoscrollTimerDelay() // tA = accelerationTime() // tD = The time we pretend has already passed when starting to scroll, // |animationTimerDelay| // // This lag provides some buffer against timer jitter so we're less likely // to hit the desired position and stop (and thus have to re-accelerate, // causing a visible hitch) while waiting for the next autoscroll increment. // // Thus, for autoscroll-timer-triggered requests, the ideal steady-state // distance to travel in each time interval is: // float animationStep = step; // Note that when we're not already animating, this is exactly the same as // the distance to the target position. We'll return to that in a moment. // // For keyboard and wheel scrolls, we don't know when the next increment // will be requested. If we set the target velocity based on how far away // from the target position we are, then for keyboard/wheel events that come // faster than the autoscroll delay, we'll asymptotically approach the // velocity needed to stay smoothly in sync with the user's actions; for // events that come slower, we'll scroll one increment and then pause until // the next event fires. float animationStep = fabs(newPos - *data->m_currentPos); // If a key is held down (or the wheel continually spun), then once we have // reached a velocity close to the steady-state velocity, we're likely to // hit the desired position at around the same time we'd expect the next // increment to occur -- bad because it leads to hitching as described above // (if autoscroll-based requests didn't result in a small amount of constant // lag). So if we're called again while already animating, we want to trim // the animationStep slightly to maintain lag like what's described above. // (I say "maintain" since we'll already be lagged due to the acceleration // during the first scroll period.) // // Remember that trimming won't cause us to fall steadily further behind // here, because the further behind we are, the larger the base step value // above. Given the scrolling algorithm in animateScroll(), the practical // effect will actually be that, assuming a constant trim factor, we'll lag // by a constant amount depending on the rate at which increments occur // compared to the autoscroll timer delay. The exact lag is given by // lag = |step| * ((r / k) - 1) // Where // r = The ratio of the autoscroll repeat delay, // ScrollbarTheme::theme()->autoscrollTimerDelay(), to the // key/wheel repeat delay (i.e. > 1 when keys repeat faster) // k = The velocity trim constant given below // // We want to choose the trim factor such that for calls that come at the // autoscroll timer rate, we'll wind up with the same lag as in the // "perfect" case described above (or, to put it another way, we'll end up // with |animationStep| == |step| * |multiplier| despite the actual distance // calculated above being larger than that). This will result in "perfect" // behavior for autoscrolling without having to special-case it. if (alreadyAnimating) animationStep /= (2.0 - ((1.0 / ScrollbarTheme::theme()->autoscrollTimerDelay()) * (0.5 * accelerationTime() + animationTimerDelay))); // The result of all this is that single keypresses or wheel flicks will // scroll in the same time period as single presses of scrollbar elements; // holding the mouse down on a scrollbar part will scroll as fast as // possible without hitching; and other repeated scroll events will also // scroll with the same time lag as holding down the mouse on a scrollbar // part. data->m_desiredVelocity = animationStep / ScrollbarTheme::theme()->autoscrollTimerDelay(); // If we're not already scrolling, start. if (!alreadyAnimating) animateScroll(data); return true; }
int main(int argc, char *argv[]) { if (argc != 6) { std::cout << "Arguments:" << std::endl << "(1) device file" << std::endl << "(2) CAN deviceID" << std::endl << "(3) sync rate [msec]" << std::endl << "(4) target velocity [rad/sec]" << std::endl << "(5) acceleration [rad/sec^2]" << std::endl << "Example 1: ./move_device /dev/pcan32 12 10 0.2 0.05" << std::endl << "Example 2 (reverse direction): " << "./move_device /dev/pcan32 12 10 -0.2 -0.05" << std::endl; return -1; } std::cout << "Interrupt motion with Ctrl-C" << std::endl; std::string deviceFile = std::string(argv[1]); uint16_t deviceID = std::stoi(std::string(argv[2])); canopen::syncInterval = std::chrono::milliseconds(std::stoi(std::string(argv[3]))); double targetVel = std::stod(std::string(argv[4])); double accel = std::stod(std::string(argv[5])); // In this example, the canopen::chainMap is constructed manually, // but there are also functions to construct it from a YAML file, // or (in ros_canopen) from the ROS parameter server, cf. user manual. canopen::using_master_thread = true; canopen::DeviceDescription CANdevice; CANdevice.name = "device1"; CANdevice.id = deviceID; CANdevice.bus = deviceFile; canopen::ChainDescription CANchain; CANchain.name = "chain1"; CANchain.devices.push_back(CANdevice); canopen::initChainMap( { CANchain } ); if (!canopen::openConnection("/dev/pcan32")) { std::cout << "Cannot open CAN device; aborting." << std::endl; return -1; } canopen::initListenerThread(); canopen::initIncomingPDOProcessorThread(); std::this_thread::sleep_for(std::chrono::seconds(1)); canopen::initMasterThread(); canopen::initNMT(); for (auto it : canopen::chainMap) it.second->CANopenInit(); // send velocity commands; these could also come from // a different thread, or even a different process // as in ros_canopen //std::thread client_thread(clientFunc); //client_thread.detach(); std::chrono::milliseconds accelerationTime ( static_cast<int>(round( 1000.0 * targetVel / accel)) ); while (false) { double vel = 0; auto startTime = std::chrono::high_resolution_clock::now(); auto tic = std::chrono::high_resolution_clock::now(); // increasing velocity ramp up to target velocity: std::cout << "Accelerating to target velocity" << std::endl; while (tic < startTime + accelerationTime) { tic = std::chrono::high_resolution_clock::now(); vel = accel * 0.000001 * std::chrono::duration_cast<std::chrono::microseconds>(tic-startTime).count(); canopen::chainMap["chain1"]->setVel({vel}); std::this_thread::sleep_for (canopen::syncInterval - (std::chrono::high_resolution_clock::now() - tic)); } // constant velocity when target vel has been reached: std::cout << "Target velocity reached!" << std::endl; canopen::chainMap["chain1"]->setVel({targetVel}); std::this_thread::sleep_for(std::chrono::seconds(1)); canopen::chainMap["chain1"]->setVel({0}); std::this_thread::sleep_for(std::chrono::milliseconds(300)); targetVel = -targetVel; accel = -accel; } while (true) std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; }
void ScrollAnimatorWin::animateScroll(PerAxisData* data) { // Note on smooth scrolling perf versus non-smooth scrolling perf: // The total time to perform a complete scroll is given by // t = t0 + 0.5tA - tD + tS // Where // t0 = The time to perform the scroll without smooth scrolling // tA = The acceleration time, // ScrollbarTheme::theme()->autoscrollTimerDelay() (see below) // tD = |animationTimerDelay| // tS = A value less than or equal to the time required to perform a // single scroll increment, i.e. the work done due to calling // client()->valueChanged() (~0 for simple pages, larger for complex // pages). // // Because tA and tD are fairly small, the total lag (as users perceive it) // is negligible for simple pages and roughly tS for complex pages. Without // knowing in advance how large tS is it's hard to do better than this. // Perhaps we could try to remember previous values and forward-compensate. // We want to update the scroll position based on the time it's been since // our last update. This may be longer than our ideal time, especially if // the page is complex or the system is slow. // // To avoid feeling laggy, if we've just started smooth scrolling we pretend // we've already accelerated for one ideal interval, so that we'll scroll at // least some distance immediately. double lastScrollInterval = data->m_currentVelocity ? (WTF::currentTime() - data->m_lastAnimationTime) : animationTimerDelay; // Figure out how far we've actually traveled and update our current // velocity. float distanceTraveled; if (data->m_currentVelocity < data->m_desiredVelocity) { // We accelerate at a constant rate until we reach the desired velocity. float accelerationRate = data->m_desiredVelocity / accelerationTime(); // Figure out whether contant acceleration has caused us to reach our // target velocity. float potentialVelocityChange = accelerationRate * lastScrollInterval; float potentialNewVelocity = data->m_currentVelocity + potentialVelocityChange; if (potentialNewVelocity > data->m_desiredVelocity) { // We reached the target velocity at some point between our last // update and now. The distance traveled can be calculated in two // pieces: the distance traveled while accelerating, and the // distance traveled after reaching the target velocity. float actualVelocityChange = data->m_desiredVelocity - data->m_currentVelocity; float accelerationInterval = actualVelocityChange / accelerationRate; // The distance traveled under constant acceleration is the area // under a line segment with a constant rising slope. Break this // into a triangular portion atop a rectangular portion and sum. distanceTraveled = ((data->m_currentVelocity + (actualVelocityChange / 2)) * accelerationInterval); // The distance traveled at the target velocity is simply // (target velocity) * (remaining time after accelerating). distanceTraveled += (data->m_desiredVelocity * (lastScrollInterval - accelerationInterval)); data->m_currentVelocity = data->m_desiredVelocity; } else { // Constant acceleration through the entire time interval. distanceTraveled = (data->m_currentVelocity + (potentialVelocityChange / 2)) * lastScrollInterval; data->m_currentVelocity = potentialNewVelocity; } } else { // We've already reached the target velocity, so the distance we've // traveled is simply (current velocity) * (elapsed time). distanceTraveled = data->m_currentVelocity * lastScrollInterval; // If our desired velocity has decreased, drop the current velocity too. data->m_currentVelocity = data->m_desiredVelocity; } // Now update the scroll position based on the distance traveled. if (distanceTraveled >= fabs(data->m_desiredPos - *data->m_currentPos)) { // We've traveled far enough to reach the desired position. Stop smooth // scrolling. *data->m_currentPos = data->m_desiredPos; data->m_currentVelocity = 0; data->m_desiredVelocity = 0; } else { // Not yet at the target position. Travel towards it and set up the // next update. if (*data->m_currentPos > data->m_desiredPos) distanceTraveled = -distanceTraveled; *data->m_currentPos += distanceTraveled; data->m_animationTimer.startOneShot(animationTimerDelay); data->m_lastAnimationTime = WTF::currentTime(); } notifyPositionChanged(); }
int main(int argc, char *argv[]) { if (argc != 6) { std::cout << "Arguments:" << std::endl << "(1) CAN interface " << std::endl << "(2) CAN deviceID" << std::endl << "(3) sync rate [msec]" << std::endl << "(4) target velocity [rad/sec]" << std::endl << "(5) acceleration [rad/sec^2]" << std::endl << "(enter acceleration '0' to omit acceleration phase)" << std::endl << "Example 1: ./move_device can0 12 10 0.2 0.05" << std::endl << "Example 2 (reverse direction): " << "./move_device can0 12 10 -0.2 -0.05" << std::endl; return -1; } std::cout << "Interrupt motion with Ctrl-C" << std::endl; std::string deviceFile = std::string(argv[1]); uint16_t CANid = std::stoi(std::string(argv[2])); canopen::syncInterval = std::chrono::milliseconds(std::stoi(std::string(argv[3]))); double targetVel = std::stod(std::string(argv[4])); double accel = std::stod(std::string(argv[5])); canopen::Device dev(CANid); canopen::devices[ CANid ] = dev; std::this_thread::sleep_for(std::chrono::milliseconds(10)); canopen::incomingPDOHandlers[ 0x180 + CANid ] = [CANid](const TPCANRdMsg m) { canopen::defaultPDO_incoming_status( CANid, m ); }; canopen::incomingPDOHandlers[ 0x480 + CANid ] = [CANid](const TPCANRdMsg m) { canopen::defaultPDO_incoming_pos( CANid, m ); }; // canopen::sendPos = canopen::defaultPDOOutgoing_interpolated; std::string chainName = "test_chain"; std::vector <uint8_t> ids; ids.push_back(CANid); std::vector <std::string> j_names; j_names.push_back("joint_1"); canopen::deviceGroups[ chainName ] = canopen::DeviceGroup(ids, j_names); canopen::init(deviceFile, chainName, canopen::syncInterval); std::this_thread::sleep_for(std::chrono::milliseconds(100)); canopen::sendSync(); canopen::setMotorState((uint16_t)CANid, canopen::MS_OPERATION_ENABLED); // start at current position dev.setDesiredPos((double)canopen::devices[CANid].getActualPos()); dev.setDesiredVel(0); canopen::sendData((uint16_t)CANid, (double)dev.getDesiredPos()); canopen::controlPDO((uint16_t)CANid, canopen::CONTROLWORD_ENABLE_MOVEMENT, 0x00); std::this_thread::sleep_for(std::chrono::milliseconds(10)); dev.setInitialized(true); if (accel != 0) { // accel of 0 means "move at target vel immediately" std::cout << "Ramping up desired velocity at acceleration " << accel << std::endl; std::chrono::milliseconds accelerationTime( static_cast<int>(round( 1000.0 * targetVel / accel)) ); double vel = 0; auto startTime = std::chrono::high_resolution_clock::now(); auto tic = std::chrono::high_resolution_clock::now(); // increasing velocity ramp up to target velocity: while (tic < startTime + accelerationTime) { tic = std::chrono::high_resolution_clock::now(); vel = accel * 0.000001 * std::chrono::duration_cast<std::chrono::microseconds>(tic-startTime).count(); std::cout << ".... vel " << vel << std::endl; dev.setDesiredVel(vel); std::this_thread::sleep_for(canopen::syncInterval - (std::chrono::high_resolution_clock::now() - tic)); canopen::sendSync(); } std::cout << "Target velocity reached\n"; } else { std::cout << "Setting desired velocity to " << targetVel << std::endl; dev.setDesiredVel(targetVel); } // constant velocity when target vel has been reached: std::cout << "Running with desired velocity " << dev.getDesiredVel() << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); //std::cout << "sending Statusword request" << std::endl; //canopen::sendSDO(CANid, canopen::STATUSWORD); std::this_thread::sleep_for(std::chrono::milliseconds(100)); while (true) { std::this_thread::sleep_for(std::chrono::seconds(500)); std::cout << "Desired Pos " << dev.getDesiredPos() << ", actual pos " << dev.getActualPos() << std::endl; canopen::sendSync(); } }