Enhancement: CircularSegment

Posts regarding potential bugs, enhancement requests, and general feedback on use of dyn4j
andreas
Posts: 8
Joined: Mon Aug 01, 2016 6:04 am

Enhancement: CircularSegment

Postby andreas » Wed Aug 03, 2016 5:12 pm

Hallo,
I adapted your Slice.java to provide a shape for circular segments instead of circular sectors. You can integrate it if you want to.

Code: Select all

/*
 * Copyright (c) 2010-2016 William Bittle  http://www.dyn4j.org/
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, this list of conditions
 *     and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 *     and the following disclaimer in the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of dyn4j nor the names of its contributors may be used to endorse or
 *     promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.dyn4j.geometry;

import org.dyn4j.DataContainer;
import org.dyn4j.geometry.AABB;
import org.dyn4j.geometry.AbstractShape;
import org.dyn4j.geometry.Circle;
import org.dyn4j.geometry.Convex;
import org.dyn4j.geometry.Feature;
import org.dyn4j.geometry.Interval;
import org.dyn4j.geometry.Mass;
import org.dyn4j.geometry.PointFeature;
import org.dyn4j.geometry.Segment;
import org.dyn4j.geometry.Shape;
import org.dyn4j.geometry.Transform;
import org.dyn4j.geometry.Transformable;
import org.dyn4j.geometry.Vector2;
import org.dyn4j.resources.Messages;

/**
 * Implementation of a CircularSegment {@link Convex} {@link Shape}.
 * <p>
 * A circular segment is a piece of a {@link Circle}.
 * <p>
 * This shape can represent any Circular Segment.
 * @author William Bittle
 */
public class CircularSegment extends AbstractShape implements Convex, Shape, Transformable, DataContainer {
   /** The total circular section in radians */
   final double theta;
   
   /** Half of theta */
   final double alpha;
   
   /** The center of the underlying Circle of this segment */
   final Vector2 circleCenter = new Vector2();
   
   /** The maximum radius of this shape rotated about its center */
   final double sliceRadius;
   
   /** The vertices of the slice */
   final Vector2[] vertices;
   
   /** The normals of the polygonal sides */
   final Vector2[] normals;
   
   /** The local x axis to track local rotation */
   final Vector2 localXAxis;
   
   /**
    * Validated constructor.
    * <p>
    * This method creates a slice of a circle with the <b>circle center</b> at the origin
    * and half of theta below the x-axis and half above.
    * @param valid always true or this constructor would not be called
    * @param radius the radius of the circular section
    * @param theta the angular extent in radians; must be greater than zero and less than or equal to &pi;
    * @param center the center
    */
   private CircularSegment(boolean valid, double radius, double theta, Vector2 center) {
      super(center, Math.max(center.x, center.distance(new Vector2(radius, 0).rotate(0.5*theta))));
      
      this.sliceRadius = radius;
      this.theta = theta;
      this.alpha = theta * 0.5;
      
      // compute the triangular section of the pie
      double x = radius * Math.cos(this.alpha);
      double y = radius * Math.sin(this.alpha);
      this.vertices = new Vector2[] {
         // the top point
         new Vector2(x, y),
         // the bottom point
         new Vector2(x, -y)
      };
      
      Vector2 v = this.vertices[0].to(this.vertices[1]);
      v.left().normalize();
      this.normals = new Vector2[] { v};

      this.localXAxis = new Vector2(1.0, 0.0);
   }
   
   /**
    * Full constructor.
    * <p>
    * This method creates a circular segment with the <b>circle center</b> at the origin
    * and half of theta below the x-axis and half above.
    * @param radius the radius of the circular section
    * @param theta the angular extent in radians; must be greater than zero and less than or equal to &pi;
    * @throws IllegalArgumentException throw if 1) radius is less than or equal to zero or 2) theta is less than or equal to zero or 3) theta is greater than 360 degrees
    */
   public CircularSegment(double radius, double theta) {
      this(validate(radius, theta), radius, theta, new Vector2(4.0 * radius * Math.sin(theta * 0.5)* Math.sin(theta * 0.5)* Math.sin(theta * 0.5) / (3.0 * (theta - Math.sin(theta))), 0));
   }

   /**
    * Validates the constructor input returning true if valid or throwing an exception if invalid.
    * @param radius the radius of the circular section
    * @param theta the angular extent in radians; must be greater than zero and less than or equal to &pi;
    * return true
    * @throws IllegalArgumentException throw if 1) radius is less than or equal to zero or 2) theta is less than or equal to zero or 3) theta is greater than 360 degrees
    */
   private static final boolean validate(double radius, double theta) {
      // check the radius
      if (radius <= 0) throw new IllegalArgumentException(Messages.getString("geometry.slice.invalidRadius"));
      // check the theta
      if (theta <= 0 || theta > 2*Math.PI) throw new IllegalArgumentException(Messages.getString("geometry.slice.invalidTheta"));
      
      return true;
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Wound#toString()
    */
   @Override
   public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append("Slice[").append(super.toString())
      .append("|Radius=").append(this.sliceRadius)
      .append("|Theta=").append(this.theta)
      .append("]");
      return sb.toString();
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Convex#getAxes(org.dyn4j.geometry.Vector2[], org.dyn4j.geometry.Transform)
    */
   @Override
   public Vector2[] getAxes(Vector2[] foci, Transform transform) {
      // get the size of the foci list
      int fociSize = foci != null ? foci.length : 0;
      // the axes of a polygon are created from the normal of the edges
      // plus the closest point to each focus
      Vector2[] axes = new Vector2[1 + fociSize];
      int n = 0;
      
      // add the normals of the sides
      axes[n++] = transform.getTransformedR(this.normals[0]);
      
      // loop over the focal points and find the closest
      // points on the polygon to the focal points
      Vector2 f1 = transform.getTransformed(this.vertices[0]);
      Vector2 f2 = transform.getTransformed(this.vertices[1]);
      for (int i = 0; i < fociSize; i++) {
         // get the current focus
         Vector2 f = foci[i];
         // find the minimum distance vertex
         // once we have found the closest point create
         // a vector from the focal point to the point
         Vector2 axis = f.to(f.distanceSquared(f1)<f.distanceSquared(f2)?f2:f1);
         // normalize it
         axis.normalize();
         // add it to the array
         axes[n++] = axis;
      }
      // return all the axes
      return axes;
   }

   /**
    * {@inheritDoc}
    * <p>
    * Returns a single point, the circle center.
    */
   @Override
   public Vector2[] getFoci(Transform transform) {
      return new Vector2[] {
         transform.getTransformed(this.circleCenter)   
      };
   }

   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Convex#getFarthestPoint(org.dyn4j.geometry.Vector2, org.dyn4j.geometry.Transform)
    */
   @Override
   public Vector2 getFarthestPoint(Vector2 vector, Transform transform) {
      Vector2 localn = transform.getInverseTransformedR(vector);
      
      // project the origin and two end points first
      if (Math.abs(localn.getAngleBetween(this.localXAxis)) > this.alpha) {
         Vector2 point = new Vector2(localn.dot(this.vertices[0])<localn.dot(this.vertices[1])?this.vertices[1]:this.vertices[0]);
         // transform the point into world space
         transform.transform(point);
         return point;
      } else {
         // NOTE: taken from Circle.getFarthestPoint with some modifications
         localn.normalize();
         localn.multiply(this.sliceRadius).add(this.circleCenter);
         transform.transform(localn);
         return localn;
      }
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Convex#getFarthestFeature(org.dyn4j.geometry.Vector2, org.dyn4j.geometry.Transform)
    */
   @Override
   public Feature getFarthestFeature(Vector2 vector, Transform transform) {
      Vector2 localAxis = transform.getInverseTransformedR(vector);
      if (Math.abs(localAxis.getAngleBetween(this.localXAxis)) <= this.alpha) {
         // then its the farthest point
         Vector2 point = this.getFarthestPoint(vector, transform);
         return new PointFeature(point);
      } else {
         return Segment.getFarthestFeature(this.vertices[0], this.vertices[1], vector, transform);
      }
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Shape#project(org.dyn4j.geometry.Vector2, org.dyn4j.geometry.Transform)
    */
   @Override
   public Interval project(Vector2 vector, Transform transform) {
      // get the world space farthest point
      Vector2 p1 = this.getFarthestPoint(vector, transform);
      Vector2 p2 = this.getFarthestPoint(vector.getNegative(), transform);
      // project the point onto the axis
      double d1 = p1.dot(vector);
      double d2 = p2.dot(vector);
      // get the interval along the axis
      return new Interval(d2, d1);
   }

   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Shape#createAABB(org.dyn4j.geometry.Transform)
    */
   @Override
   public AABB createAABB(Transform transform) {
      Interval x = this.project(Vector2.X_AXIS, transform);
      Interval y = this.project(Vector2.Y_AXIS, transform);
      
      return new AABB(x.getMin(), y.getMin(), x.getMax(), y.getMax());
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Shape#createMass(double)
    */
   @Override
   public Mass createMass(double density) {
      final double r2 = this.sliceRadius * this.sliceRadius;
      final double sint=Math.sin(this.theta);
      final double diff=(this.theta-sint);
      final double m = density * 0.5 * r2 * diff;
      // inertia about z: http://www.efunda.com/math/areas/CircularSegment.cfm
      double sin2a = Math.sin(this.alpha);
      sin2a*=sin2a;
      double I = r2*r2*(0.25*(this.theta-sint+2.0/3.0*sint*sin2a)-8.0/9.0*sin2a*sin2a*sin2a/diff);
      return new Mass(super.center, m, I);
   }

   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Shape#getRadius(org.dyn4j.geometry.Vector2)
    */
   @Override
   public double getRadius(Vector2 center) {
      if (Math.abs(center.getAngleBetween(localXAxis))>Math.PI-alpha) {
         return super.radius + center.getMagnitude();
      }
      final double d0=center.distance(this.vertices[0]);
      final double d1=center.distance(this.vertices[0]);
      return d0>d1?d0:d1;
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.Shape#contains(org.dyn4j.geometry.Vector2, org.dyn4j.geometry.Transform)
    */
   @Override
   public boolean contains(Vector2 point, Transform transform) {
      // see if the point is in the circle
      // transform the point into local space
      Vector2 lp = transform.getInverseTransformed(point);
      // get the transformed radius squared
      double radiusSquared = this.sliceRadius * this.sliceRadius;
      // create a vector from the circle center to the given point
      Vector2 v = this.circleCenter.to(lp);
      if (v.getMagnitudeSquared() <= radiusSquared) {
         // if its in the circle then we need to make sure its in the section
         return lp.subtract(this.vertices[0]).dot(this.localXAxis)>=0;         
      }
      // if its not in the circle then no other checks need to be performed
      return false;
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.AbstractShape#rotate(double, double, double)
    */
   @Override
   public void rotate(double theta, double x, double y) {
      // rotate the centroid
      super.rotate(theta, x, y);
      // rotate the center of the underlying circle
      this.circleCenter.rotate(theta, x, y);
      // rotate the pie vertices
      for (int i = 0; i < this.vertices.length; i++) {
         this.vertices[i].rotate(theta, x, y);
      }
      // rotate the pie normals
      for (int i = 0; i < this.normals.length; i++) {
         this.normals[i].rotate(theta);
      }
      // rotate the local x axis
      this.localXAxis.rotate(theta);
   }
   
   /* (non-Javadoc)
    * @see org.dyn4j.geometry.AbstractShape#translate(double, double)
    */
   @Override
   public void translate(double x, double y) {
      // translate the centroid
      super.translate(x, y);
      // translate the center of the underlying circle
      this.circleCenter.add(x, y);
      // translate the pie vertices
      for (int i = 0; i < this.vertices.length; i++) {
         this.vertices[i].add(x, y);
      }
   }

   /**
    * Returns the rotation about the local center in radians.
    * @return double the rotation in radians
    */
   public double getRotation() {
      return Vector2.X_AXIS.getAngleBetween(this.localXAxis);
   }
   
   /**
    * Returns the angular extent of the segment in radians.
    * @return double
    */
   public double getTheta() {
      return this.theta;
   }

   /**
    * Returns the radius of the underlying circle.
    * <p>
    * This differs from the {@link #getRadius()} since it returns the
    * maximum rotation radius of the shape about its center. This method
    * returns the radius passed in at creation.
    * @return double
    */
   public double getSliceRadius() {
      return this.sliceRadius;
   }
   
   /**
    * Returns the center of the circle.
    * @return {@link Vector2}
    */
   public Vector2 getCircleCenter() {
      return this.vertices[0];
   }
}


Greetings
Andreas

Return to “Bugs, Enhancements, Feedback”

Who is online

Users browsing this forum: No registered users and 2 guests