Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to scale an object from the top left in react native using matrices?

I was reading this article which explains how to do rotation transforms in react native using MatrixMath. I am trying to animate the scale of an object, not the rotation, and I want it to scale using an origin at the top left, rather than center, of the object. Can anyone explain how to do this?

The relevant bits of code for the rotation matrix are:

const matrix = transformUtil.rotateX(dx);
transformUtil.origin(matrix, { x: 0, y, z: 0 });

const perspective = this.props.perspective || rootDefaultProps.perspective;

ref.setNativeProps({
  style: {
    transform: [
      { perspective },
      { matrix },
    ],
  },
});

and, from the transformUtil:

import MatrixMath from 'react-native/Libraries/Utilities/MatrixMath';

function transformOrigin(matrix, origin) {
  const { x, y, z } = origin;

  const translate = MatrixMath.createIdentityMatrix();
  MatrixMath.reuseTranslate3dCommand(translate, x, y, z);
  MatrixMath.multiplyInto(matrix, translate, matrix);

  const untranslate = MatrixMath.createIdentityMatrix();
  MatrixMath.reuseTranslate3dCommand(untranslate, -x, -y, -z);
  MatrixMath.multiplyInto(matrix, matrix, untranslate);
}

function rotateX(deg) {
  const rad = (Math.PI / 180) * deg;
  const cos = Math.cos(rad);
  const sin = Math.sitransfn(rad);
  return [
    1, 0, 0, 0,
    0, cos, -sin, 0,
    0, sin, cos, 0,
    0, 0, 0, 1,
  ];
}

export default {
  rotateX,
  origin: transformOrigin,
};
like image 305
mheavers Avatar asked Jun 22 '17 19:06

mheavers


1 Answers

Before diving into solution of the problem described, I would highly recommend for anyone reading this to learn (or brush up on) matrix multiplication. There are a few great resources out there to do it, but my personal favourite is Khan Academy.

If you prefer to just read the code, here is working solution in Snack: https://snack.expo.io/BJnDImQlr

Breakdown:

First thing we need to do is to set the transformation origin of the object that we are going to scale. We will be using part of the transformOrigin function from the article that OP included in their question. However, we only need to modify the origin once, since there is no need to reset it back to the top (specific animation in the article required it to be returned to the top).


    function transformOrigin(matrix, origin) {
        const { x, y, z } = origin;

        const translate = MatrixMath.createIdentityMatrix();
        MatrixMath.reuseTranslate3dCommand(translate, x, y, z);
        MatrixMath.multiplyInto(matrix, translate, matrix);
    }

We can scale any object represented by an appropriate matrix (ie. MatrixMath.createIdentityMatrix) using matrix multiplication. If we multiply matrix described below by target object matrix, we will end up with that same object matrix scaled by x.


    function scale(x) {
        return [
            x, 0, 0, 0,
            0, x, 0, 0,
            0, 0, x, 0,
            0, 0, 0, 1
        ];
    }

Now we need to put everything together.

  • Pass in the object of interest ref and its properties.
  • Generate an identity matrix for that object.
  • Increase scale of that object by matrix multiplication.
  • Move object origin to the desired upper left position (xAxis: 0, yAxis: 0).
  • Use MatrixMath.multiplyInto to process all previous steps via matrix multiplication.
  • Apply transformation to the target ref object via setNativeProps.

    function transformScale(ref, scaleBy, width, height) {
        const matrix = MatrixMath.createIdentityMatrix();
        const toScale = scale(scaleBy);

        transformOrigin(matrix, {
            x: (width * scaleBy - width) / 2,
            y: (height * scaleBy - height) / 2,
            z: 0
        });

        MatrixMath.multiplyInto(matrix, matrix, toScale);

        ref.setNativeProps({
            style: { transform: [{ matrix }] }
        });
    }

Now, we add all of the methods described above into the React Component. If you want to increase or decrease scale of the object, change the second parameter in transformScale(this._target, 3, width, height). You can even go as far as setting scaleBy to be a dynamic value and make an animation with it.


    export default class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                width: 0,
                height: 0
            };
        }

        handleBaseLayout = (e) => {
            const { width, height } = e.nativeEvent.layout;

            this.setState({ width, height }, () => {
                transformScale(this._target, 3, width, height);
            });
        };

        render() {
            return (
                <View style={styles.container}>
                    <View
                        style={styles.target}
                        ref={c => (this._target = c)}
                        onLayout={this.handleBaseLayout}
                    />
                </View>
            );
        }
    }

    const styles = StyleSheet.create({
        container: {
            flex: 1,
            justifyContent: 'center',
            paddingTop: Constants.statusBarHeight,
            backgroundColor: '#ecf0f1',
            padding: 8,
        },
        target: {
            width: 100,
            height: 100,
            backgroundColor: 'blue',
        },
    });

like image 59
Artem K Avatar answered Sep 28 '22 07:09

Artem K