/* A bicycle wheel with information necessary to compute information about its spokes, including spoke lengths. */ public class Wheel { /* Create a new wheel with the specified number of spokes, lacing, offset, rim, hub, and spokes. */ public Wheel (int n, int dl, int nl, double o, Rim r, Hub h, Spoke ds, Spoke ns) { numberOfSpokes = n; driveLacing = dl; nonDriveLacing = nl; offset = o; rim = r; hub = h; driveSpoke = ds; nonDriveSpoke = ns; } /* Calculate the spoke pull angle for the drive-side spokes in degrees. Choose a lacing pattern that makes this close to tangential (90 degees). */ public double drivePullAngle () { if (numberOfSpokes == 0) return 0.0; /* avoid divide by zero */ TwoDPoint hubEnd = new TwoDPoint (hub.driveFlangeRadius, 0.0, TwoDPoint.POLAR); TwoDPoint rimEnd = new TwoDPoint (rim.rimDiameter / 2.0, driveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, TwoDPoint.POLAR); return rimEnd.angleTo (hubEnd) * 180.0 / Math.PI; } /* Calculate the spoke pull angle for the non-drive-side spokes in degrees. Choose a lacing pattern that makes this close to tangential (90 degees). */ public double nonDrivePullAngle () { if (numberOfSpokes == 0) return 0.0; /* avoid divide by zero */ TwoDPoint hubEnd = new TwoDPoint (hub.nonDriveFlangeRadius, 0.0, TwoDPoint.POLAR); TwoDPoint rimEnd = new TwoDPoint (rim.rimDiameter / 2.0, nonDriveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, TwoDPoint.POLAR); return rimEnd.angleTo (hubEnd) * 180.0 / Math.PI; } /* Compute the spoke tensions on both sides in kg. Assumes max tension is 180 kg and desired tension is 3/4 of this. Store results in instance variables. */ private void computeSpokeTensions () { ThreeDPoint hubEnd; ThreeDPoint rimEnd; hubEnd = new ThreeDPoint (hub.driveFlangeRadius, 0.0, hub.driveFlangeInset); rimEnd = new ThreeDPoint (rim.rimDiameter / 2, driveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, hub.axleLength / 2.0 - rim.driveHoleOffset + offset); double driveEffectiveness = (rimEnd.getX () - hubEnd.getX ()) / (hubEnd.distanceTo (rimEnd)); hubEnd = new ThreeDPoint (hub.nonDriveFlangeRadius, 0.0, hub.nonDriveFlangeInset); rimEnd = new ThreeDPoint (rim.rimDiameter / 2.0, nonDriveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, hub.axleLength / 2.0 - rim.nonDriveHoleOffset - offset); double nonDriveEffectiveness = (rimEnd.getX () - hubEnd.getX ()) / (hubEnd.distanceTo (rimEnd)); driveTension = 0.75; /* work in fractions of max for now */ nonDriveTension = 0.75; /* work in fractions of max for now */ if (driveEffectiveness < nonDriveEffectiveness) driveTension = nonDriveTension * nonDriveEffectiveness / driveEffectiveness; else nonDriveTension = driveTension * driveEffectiveness / nonDriveEffectiveness; double maxTension = Math.max (driveTension, nonDriveTension); if (maxTension > 1.0) { driveTension = driveTension / maxTension; nonDriveTension = nonDriveTension / maxTension; } driveTension *= 180.0; /* convert from fractions of max to kg */ nonDriveTension *= 180.0; /* convert from fractions of max to kg */ } /* The drive side spoke tension in kg. */ public double driveSpokeTension () { computeSpokeTensions (); return driveTension; } /* The non-drive side spoke tension in kg. */ public double nonDriveSpokeTension () { computeSpokeTensions (); return nonDriveTension; } /* Length of drive-side spokes ignoring stretch. */ private double driveSpokeDistance () { if (numberOfSpokes == 0) return 0.0; /* avoid divide by zero */ ThreeDPoint hubEnd = new ThreeDPoint (hub.driveFlangeRadius, 0.0, hub.driveFlangeInset); ThreeDPoint rimEnd = new ThreeDPoint (rim.rimDiameter / 2.0, driveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, hub.axleLength / 2.0 - rim.driveHoleOffset + offset); return hubEnd.distanceTo (rimEnd) - hub.holeDiameter / 2.0; } /* Length of non-drive-side spokes ignoring stretch. */ private double nonDriveSpokeDistance () { if (numberOfSpokes == 0) return 0.0; /* avoid divide by zero */ ThreeDPoint hubEnd = new ThreeDPoint (hub.nonDriveFlangeRadius, 0.0, hub.nonDriveFlangeInset); ThreeDPoint rimEnd = new ThreeDPoint (rim.rimDiameter / 2.0, nonDriveLacing * 2.0 / numberOfSpokes * 2.0 * Math.PI, hub.axleLength / 2.0 - rim.nonDriveHoleOffset - offset); return hubEnd.distanceTo (rimEnd) - hub.holeDiameter / 2.0; } /* Compute the drive-side spoke stretch from the drive-side spoke tension and the drive-side spoke data. Formulae based on ones taken from The Bicycle Wheel by Jobst Brandt. */ public double driveSpokeStretch () { double radius; double area; double modulus = 21000; /* modulus of elasticity of steel kg/mm^2 */ double length; double result; radius = driveSpoke.buttDiameter / 2; area = Math.PI * radius * radius; length = driveSpoke.buttLength; result = driveSpokeTension () * length / area / modulus; /* butt stretch */ radius = driveSpoke.spokeDiameter / 2; area = Math.PI * radius * radius; length = driveSpokeDistance () - driveSpoke.buttLength; result += driveSpokeTension () * length / area / modulus; /* end stretch */ return result; } /* Compute the non-drive-side spoke stretch from the non-drive-side spoke tension and the non-drive-side spoke data. Formulae based on ones taken from The Bicycle Wheel by Jobst Brandt. */ public double nonDriveSpokeStretch () { double radius; double area; double modulus = 21000; /* modulus of elasticity of steel kg/mm^2 */ double length; double result; radius = nonDriveSpoke.buttDiameter / 2; area = Math.PI * radius * radius; length = nonDriveSpoke.buttLength; result = nonDriveSpokeTension () * length / area / modulus; /* butt stretch */ radius = nonDriveSpoke.spokeDiameter / 2; area = Math.PI * radius * radius; length = nonDriveSpokeDistance () - nonDriveSpoke.buttLength; result += nonDriveSpokeTension () * length / area / modulus; /* end stretch */ return result; } /* Actually calculate the spoke length for the drive side. Result should be rounded down to nearest available length. */ public double driveSpokeLength () { return driveSpokeDistance () - driveSpokeStretch (); } /* Actually calculate the spoke length for the non-drive side. Result should be rounded down to nearest available length. */ public double nonDriveSpokeLength () { return nonDriveSpokeDistance () - nonDriveSpokeStretch (); } public int numberOfSpokes; public int driveLacing; public int nonDriveLacing; public double offset; public Rim rim; public Hub hub; public Spoke driveSpoke; public Spoke nonDriveSpoke; private double driveTension; private double nonDriveTension; }