0
\$\begingroup\$

I'm working on generating objects in sine wave that lets you specify parameters that are start x, end x, start y, and end y position.

Below is the method that I used to generate the debris

void generateWaveDebris(float startX, float endX, float startY, float endY, int numberToGenerate, bool randomizeDebrisDirection = false, bool randomizeDebrisFallSpeed = false, int[] numbersToSkip = null) {
    float totalXDistance = endX - startX;
    float xPositionOffset = 0f;
    float xStepSizeForGeneration = totalXDistance/numberToGenerate;
    xPositionOffset += xStepSizeForGeneration/2;

    float totalYDistance = endY - startY;
    float yPositionOffset = 0f;
    float yStepSizeForGeneration = totalYDistance/numberToGenerate;
    yPositionOffset += yStepSizeForGeneration/2;

    int generatedDebrisTracker = 0;

    float debrisRotation = 0;
    float debrisRotationSpeed = Random.Range(debrisImageRotationSpeedMin, debrisImageRotationSpeedMax);
    float debrisFallSpeed = Random.Range(debrisFallSpeedMin, debrisFallSpeedMax);

    bool skipThisDebris = false;

    Vector3 newPosition = Vector3.zero;
    newPosition.z = defaultGeneratedDebrisZPosition;
    while(generatedDebrisTracker < numberToGenerate) {

        if(numbersToSkip != null) {
            foreach(int number in numbersToSkip) {
                if(generatedDebrisTracker == number) {                          
                    skipThisDebris = true;
                }
            }
        }

        // X Tracking ----------------
        newPosition.x = startX + xPositionOffset;
        xPositionOffset += xStepSizeForGeneration;
        //----------------

        // Y Tracking ----------------
        newPosition.y = startY + yPositionOffset;
        yPositionOffset += yStepSizeForGeneration;
        //----------------

        float tempX = newPosition.x;
        float tempY = newPosition.y;

        newPosition.x += Mathf.Sin(normalizeValue(tempY, startY, endY, 0f, 359.9f)) * 5;
        newPosition.y += Mathf.Cos(normalizeValue(tempX, startX, endX, 0f, 359.9f)) * 5;

        if(!skipThisDebris) {
            GameObject newDebris = null;
            newDebris = generateRandomDebris(newPosition);

            if(newDebris != null) {
                Debris newDebrisReference;
                newDebrisReference = newDebris.GetComponent<Debris>();

                newDebrisReference.defaultObjectZRotation = 0;

                //This will randomize the direction that the debris falls in if 
                //it is set to true when the method is called
                if(randomizeDebrisDirection) {
                    newDebrisReference.defaultObjectZRotation = Random.Range(debrisRotationMin, debrisRotationMax);
                }
                else {
                    newDebrisReference.defaultObjectZRotation = debrisRotation;
                }
                //This will randomize the fall speed of the debris if
                //it is set to true when the method is called
                if(randomizeDebrisFallSpeed) {
                    newDebrisReference.fallSpeed = Random.Range(debrisFallSpeedMin, debrisFallSpeedMax);
                }
                else {
                    newDebrisReference.fallSpeed = debrisFallSpeed;
                }

                newDebrisReference.rotationSpeed = debrisRotationSpeed;

                newDebris.transform.parent = transform;
            }
        }
        //-----------------------------
        generatedDebrisTracker += 1;

        //Resets for next loop
        skipThisDebris = false;
    }
}

It's important to note that the sections that matter are the lines:

newPosition.x += Mathf.Sin(normalizeValue(tempY, startY, endY, 0f, 359.9f)) * 5;
newPosition.y += Mathf.Cos(normalizeValue(tempX, startX, endX, 0f, 359.9f)) * 5;

When the line's start x and end x are the same, the wave generates fine. However when the line is diagonal it gets all sorts of messed up.

I know that there are ways to generate a sin wave and then use a linear transformation matrix to rotate it, however I've had a ton of issues getting that going, and it doesn't resolve one of my requirements where I specify the start x, end x, start y, and end y. This is because when the linear transformation rotation matrix is applied it doesn't keep the line scaled to the points I want.

So my question is: Is there a way to make a sine wave that can be oriented in any rotation, as well as specifying its start and end points for the x and y values?

If there is any more information I can provide, or anything I can do to help facilitate answers I would be more than happy to provide it.

\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

There are a number of ways to do this, which end up being equivalent, but I'll explain how to do it in terms of basic vectors.

(By the way, I strongly recommend that you stop using paired 'x' and 'y' numeric variables and start using vectors for everything; it makes many things easier to express, and reduces the chance of making a mistake.)

Conceptually, we start with a line from the start point to the end point, break it up into segments, and then displace the points of the segments according to a sine function. I'm going to write an example in JavaScript and write my own vector functions, but it should be easy to translate.

A key point to note: I've written this code in terms of 2D vectors, but the basic algorithm is completely generic and will work equally well with 3D vectors. This is one of the key advantages of working with vectors: the code is designed to generate a wave in a 2D plane, and doesn't care how many extra dimensions it's not looking at there might be.

First, the setup:

<title>Sine Wave Demo</title>
<body style='margin: 0;'>
<canvas id='c' width='400' height='400'></canvas>
<script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');

Vector functions — you should find equivalents to all of these in Unity's vector libraries.

function vmul(s, a) { return [s*a[0], s*a[1]]; }
function vadd(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
function vsub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
function vlength(a) { return Math.sqrt(a[0]*a[0] + a[1]*a[1]); }
function vnormalize(a) { return vmul(1/vlength(a), a); }

vrotate is a little tricky: it is a shortcut to rotate a 2D vector by 90 degrees. If you're working in 3D vectors, then you'll probably use a "rotate about the Z axis" function instead. It doesn't matter how you do it as long as the resulting vector is perpendicular to the input (if it isn't, the wave will be skewed, which might be an interesting effect).

function vrotate(a) { return [a[1], -a[0]]; }

Now the actual algorithm. start is the start point of the wave, end the ending point (both vectors). amplitude is the amplitude of the wave: the maximum distance away from the center-line it reaches. numPoints is the number of points to generate. numWaves is the number of repetitions of the sine wave (I think your code has this at 1).

function drawSine(start, end, amplitude, numPoints, numWaves) {

displacement is the vector from the beginning to the end, independent of location.

  var displacement = vsub(end, start);

We will want to use the length of it.

  var length = vlength(displacement);

parallel is a unit vector (vector of length 1) in the same direction. This gives us the basis for computing points along the wave.

  var parallel = vnormalize(displacement);

perpendicular is the key ingredient you didn't have: it points perpendicular to the line between the start and end points, and is what we use to displace the points according to the sine function.

(Linear algebra side note: If you combine parallel and perpendicular as the columns of a matrix, then that matrix is the transformation matrix of a rotation matching the orientation of the line!)

  var perpendicular = vrotate(parallel);

These will be explained later.

  var distanceScale = length / (numPoints - 1);
  var sineScale = 2 * Math.PI * numWaves / numPoints;
  for (var i = 0; i < numPoints; i++) {

Compute the appropriate sine function. sineScale was defined to make numWaves full cycles (2 * Math.PI) when numPoints reaches its limit.

    var sineValue = amplitude * Math.sin(i * sineScale);

pointOnLine is a point which is at step i along the straight line between start and end. This is just like linear interpolation in disguise. distanceScale was divided by numPoints - 1 so that the last loop iteration, where i == numPoints - 1, hits the end point exactly.

    var pointOnLine = vadd(start, vmul(i * distanceScale, parallel));

Here's the key part: pointOnSine is moved away from pointOnLine perpendicularly to the line according to the sine function.

    var pointOnSine = vadd(pointOnLine, vmul(sineValue, perpendicular));

Now draw something. In your application you'd create the debris object instead, here.

    ctx.fillText('x', pointOnSine[0], pointOnSine[1]);
  }
}

Some additional code to demonstrate; move your mouse around to see the wave change.

var target = [400, 400];
function go() {
  drawSine([10, 10], target, 30, 70, 2.5);
}
window.addEventListener('mousemove', function(e) {
  target[0] = e.clientX;
  target[1] = e.clientY;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  go();
});
function size() {
  canvas.width = document.body.offsetWidth;
  canvas.height = document.body.offsetHeight;
  go();
}
size();
</script>

Here's all of the above code collected so you can actually run it:

<title>Sine Wave Demo</title>
<body style='margin: 0;'>
<canvas id='c' width='400' height='400'></canvas>
<script>
function vmul(s, a) { return [s*a[0], s*a[1]]; }
function vadd(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
function vsub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
function vlength(a) { return Math.sqrt(a[0]*a[0] + a[1]*a[1]); }
function vnormalize(a) { return vmul(1/vlength(a), a); }
function vrotate(a) { return [a[1], -a[0]]; }

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');

function drawSine(start, end, amplitude, numPoints, numWaves) {
  var displacement = vsub(end, start);
  var length = vlength(displacement);
  var parallel = vnormalize(displacement);
  var perpendicular = vrotate(parallel);
  var distanceScale = length / (numPoints - 1);
  var sineScale = 2 * Math.PI * numWaves / numPoints;
  for (var i = 0; i < numPoints; i++) {
    var sineValue = amplitude * Math.sin(i * sineScale);
    var pointOnLine = vadd(start, vmul(i * distanceScale, parallel));
    var pointOnSine = vadd(pointOnLine, vmul(sineValue, perpendicular));
    ctx.fillText('x', pointOnSine[0], pointOnSine[1]);
  }
}

var target = [400, 400];
function go() {
  drawSine([10, 10], target, 30, 70, 2.5);
}
window.addEventListener('mousemove', function(e) {
  target[0] = e.clientX;
  target[1] = e.clientY;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  go();
});
function size() {
  canvas.width = document.body.offsetWidth;
  canvas.height = document.body.offsetHeight;
  go();
}
size();
</script>

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .