void ObjectBase::onSimulation(float timeStep) { Point2 delta = m_velocityRelativeToParent * timeStep; if (! m_collidesWithCells || (delta.squaredLength() < 1e-6f)) { // Case without collisions m_frameRelativeToJoint.translation += delta; } else { if (! isRoot()) { report(format("ObjectBase::collidesWithCells must be false for non-root objects. (%d)", m_id.c_str()), ReportLevel::ERROR); m_frameRelativeToJoint.translation += delta; return; } // Collision case const float speed = m_velocityRelativeToParent.length(); // Algorithm: Find all tiles within the maximum extent of the object's // movement (i.e., ignoring direction) plus its radius. Reduce to // the edges (line segments) of non-traversable tiles. // A static object with default radius can hit four tiles; // one that is moving might typically hit nine, and there is no // upper bound. Map::SmallEdgeArray edgesInDisk; const Frame2D& frame = this->frame(); m_world.map->getEdgesInDisk(frame.translation, delta.length() + m_collisionRadius, m_elevation, LayerIndex(1), edgesInDisk); // Transform the edges into the parent's object space const Frame2D& parentFrame = (isRoot() ? Frame2D() : m_world.objectTable.valueFromKey(m_parent)->frame()); for (int e = 0; e < edgesInDisk.size(); ++e) { LineSegment2D& edge = edgesInDisk[e]; debugDrawEdgeArray.append(edge); // TODO: Remove edge = parentFrame.bringFromParentSpace(edge); } const int maxIterations = 20; // Check for collisions with the map edges, adjusting velocity into the unblocked direction each time int iterations; for (iterations = 0; (iterations < maxIterations) && (timeStep > 1e-6); ++iterations) { // Find the first collision float firstCollisionTime = finf(); Point2 firstCollisionLocation; for (int e = 0; e < edgesInDisk.size(); ++e) { const LineSegment2D& edge = edgesInDisk[e]; // Recall that everything is in body space, now Point2 collisionLocation; const float collisionTime = movingDiskFixedLineSegmentCollisionTime(m_frameRelativeToJoint.translation, m_collisionRadius, m_velocityRelativeToParent, edge, collisionLocation); if (collisionTime < inf()) { debugDrawPointArray.append(collisionLocation); } if (collisionTime < firstCollisionTime) { firstCollisionTime = collisionTime; firstCollisionLocation = collisionLocation; } } // Resolve the collision if it happens before the end of the time step if (firstCollisionTime < timeStep) { // Advance to just before the collision firstCollisionTime = max(firstCollisionTime - 1e-5f, 0.0f); m_frameRelativeToJoint.translation += m_velocityRelativeToParent * firstCollisionTime; timeStep -= firstCollisionTime; const Vector2 normal = (m_frameRelativeToJoint.translation - firstCollisionLocation).directionOrZero(); // Alter velocity at the collision by removing the component of the velocity along the collision normal m_velocityRelativeToParent -= normal * min(normal.dot(m_velocityRelativeToParent), 0.0f); // Restore full speed, deflecting movement m_velocityRelativeToParent = m_velocityRelativeToParent.directionOrZero() * speed; if (m_velocityRelativeToParent.squaredLength() < 1e-6f) { // Unable to proceed with movement because there is no velocity left timeStep = 0; } } else { // Go to the end of the time step firstCollisionTime = timeStep; m_frameRelativeToJoint.translation += m_velocityRelativeToParent * timeStep; timeStep = 0; } } if (iterations == maxIterations) { report("Hit maximum number of iterations in ObjectBase::onSimulation collision resolution.", ReportLevel::WARNING); } } }
void CollisionShape2DEditor::set_handle(int idx, Point2& p_point) { switch ( shape_type ) { case CAPSULE_SHAPE: { if (idx < 2) { Ref<CapsuleShape2D> capsule = node->get_shape(); real_t parameter = Math::abs(p_point[idx]); if (idx==0) { capsule->set_radius(parameter); } else if (idx==1){ capsule->set_height(parameter*2 - capsule->get_radius()*2); } canvas_item_editor->get_viewport_control()->update(); } } break; case CIRCLE_SHAPE: { Ref<CircleShape2D> circle = node->get_shape(); circle->set_radius(p_point.length()); canvas_item_editor->get_viewport_control()->update(); } break; case CONCAVE_POLYGON_SHAPE: { } break; case CONVEX_POLYGON_SHAPE: { } break; case LINE_SHAPE: { if (idx<2) { Ref<LineShape2D> line = node->get_shape(); if (idx==0){ line->set_d(p_point.length()); }else{ line->set_normal(p_point.normalized()); } canvas_item_editor->get_viewport_control()->update(); } } break; case RAY_SHAPE: { Ref<RayShape2D> ray = node->get_shape(); ray->set_length(Math::abs(p_point.y)); canvas_item_editor->get_viewport_control()->update(); } break; case RECTANGLE_SHAPE: { if (idx<2) { Ref<RectangleShape2D> rect = node->get_shape(); Vector2 extents = rect->get_extents(); extents[idx] = p_point[idx]; rect->set_extents(extents.abs()); canvas_item_editor->get_viewport_control()->update(); } } break; case SEGMENT_SHAPE: { if (edit_handle < 2) { Ref<SegmentShape2D> seg = node->get_shape(); if (idx==0) { seg->set_a(p_point); } else if (idx==1) { seg->set_b(p_point); } canvas_item_editor->get_viewport_control()->update(); } } break; } }
bool CircleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return p_point.length() < get_radius() + p_tolerance; }