Closed NinjaXY closed 6 years ago
@NinjaXY The problem with the hit test is that you have them in separate svg roots. I guess the later one (higher z-index) will intersect with the touch event and override the earlier one. To get correct handling of 3d, we would have to send 3x3 matrices to the native implementation, currently we send a 2x3 matrix, as svg currently only supports 2d transforms, except on the root element. You can find some inspiration here https://medium.com/@youngchanje/implementing-cube-in-react-native-cb61b9a7e8c3
You could calculate path data yourself, and do the 3d transforms of the coordinates yourself before sending them to the native side, but then you have to handle z/buffering & occlusion culling yourself. Or use something like: http://seenjs.io/demo-multi-views.html or http://kovacsv.github.io/JSModeler/documentation/examples/svgto3d.html
Perhaps a better alternative would be for you to implement support for the upcoming svg 3d transforms: https://www.w3.org/TR/SVG-Transforms/
Actually, for 3d content and transforms, opengl will be a much better match. https://www.npmjs.com/package/react-native-gl-model-view https://github.com/gre/gl-react-native-v2 Or, if you only need to rotate rectangles, then you can just use plain react-native views with a background color and the normal 3d transforms.
@NinjaXY Here is an example of animating 2.5d(getting quite different results on android and ios, but something to start working/investigating from at least) and correct press handlers:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import { StyleSheet, View, Dimensions, Animated } from 'react-native';
import { Svg, G, Circle, Path, Rect } from 'react-native-svg';
import ZoomableSvg from 'zoomable-svg';
const { width, height } = Dimensions.get('window');
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
const AnimatedRect = Animated.createAnimatedComponent(Rect);
class SvgRoot extends Component {
state = {
toggle: false,
zAnim: new Animated.Value(0),
initAnim: new Animated.Value(0),
};
componentDidMount() {
Animated.timing(
// Animate over time
this.state.initAnim,
{
toValue: 1,
duration: 3000,
useNativeDriver: false,
},
).start();
}
moveV = () => {
Animated.timing(this.state.zAnim, {
toValue: this.state.zAnim._value > 0.5 ? 0 : 1,
duration: 1000,
useNativeDriver: false,
}).start();
};
toggle = () => {
this.setState(({ toggle }) => ({ toggle: !toggle }));
};
alert = () => alert(1);
noop = () => {};
render() {
const { toggle, zAnim, initAnim } = this.state;
let rotZ = zAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '-45deg'],
});
let translateRootX = initAnim.interpolate({
inputRange: [0, 1],
outputRange: [0, 50],
});
let translateRectY = initAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0', '50'],
});
const { transform } = this.props;
return (
<AnimatedSvg
width={width}
height={height}
style={{
transform: [
{ perspective: 850 },
{ rotateX: '70deg' },
{ rotateZ: rotZ },
{ translateX: translateRootX },
],
}}
>
<G transform={transform}>
<AnimatedRect
onPressIn={this.moveV}
y={translateRectY}
x="5"
width="90"
height="90"
fill="rgb(0,0,255)"
strokeWidth="3"
stroke="rgb(0,0,0)"
/>
<Rect
x="5"
y="5"
width="55"
height="55"
fill="white"
onPressIn={this.alert}
/>
<Circle
cx="32"
cy="32"
r="4.167"
fill={toggle ? 'red' : 'blue'}
onPress={this.toggle}
/>
<Path
d="M55.192 27.87l-5.825-1.092a17.98 17.98 0 0 0-1.392-3.37l3.37-4.928c.312-.456.248-1.142-.143-1.532l-4.155-4.156c-.39-.39-1.076-.454-1.532-.143l-4.928 3.37a18.023 18.023 0 0 0-3.473-1.42l-1.086-5.793c-.103-.543-.632-.983-1.185-.983h-5.877c-.553 0-1.082.44-1.185.983l-1.096 5.85a17.96 17.96 0 0 0-3.334 1.393l-4.866-3.33c-.456-.31-1.142-.247-1.532.144l-4.156 4.156c-.39.39-.454 1.076-.143 1.532l3.35 4.896a18.055 18.055 0 0 0-1.37 3.33L8.807 27.87c-.542.103-.982.632-.982 1.185v5.877c0 .553.44 1.082.982 1.185l5.82 1.09a18.013 18.013 0 0 0 1.4 3.4l-3.31 4.842c-.313.455-.25 1.14.142 1.53l4.155 4.157c.39.39 1.076.454 1.532.143l4.84-3.313c1.04.563 2.146 1.02 3.3 1.375l1.096 5.852c.103.542.632.982 1.185.982h5.877c.553 0 1.082-.44 1.185-.982l1.086-5.796c1.2-.354 2.354-.82 3.438-1.4l4.902 3.353c.456.313 1.142.25 1.532-.142l4.155-4.154c.39-.39.454-1.076.143-1.532l-3.335-4.874a18.016 18.016 0 0 0 1.424-3.44l5.82-1.09c.54-.104.98-.633.98-1.186v-5.877c0-.553-.44-1.082-.982-1.185zM32 42.085c-5.568 0-10.083-4.515-10.083-10.086 0-5.568 4.515-10.084 10.083-10.084 5.57 0 10.086 4.516 10.086 10.083 0 5.57-4.517 10.085-10.086 10.085z"
fill="blue"
onPress={this.noop}
/>
</G>
</AnimatedSvg>
);
}
}
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<ZoomableSvg
align="mid"
vbWidth={100}
vbHeight={100}
width={width}
height={height}
meetOrSlice="slice"
svgRoot={SvgRoot}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
},
});
Seems the perspective values are interpreted differently on android and ios.
@msand Really thanks for your detailed reply.
In your last example code, actually I want the rects move vertically along the screen side separatelly. To achieve this, especially after I rotated the rects with X axis, I have to use translateZ
or wrap each of them into a View
and then use translateY
.
All I want is to do some 3d transforms with several simple svg images, is there any easy way to achieve this, or I have to do with the matrix things?
Well, if you add another dimension to your information and make your rendering dependent on it, then you have to do the required math to have the correct results ;) It doesn't have to be matrices, but the mathematically equivalent computation has to be done somehow.
The native rendering stack allows for 3x3 matrices. if the z-order (what objects are in front of what other objects, and thus occluding them, as they will be rendered in DOM order rather than furthest away first) doesn't change over time/interaction, then it would be enough to add support for sending 3x3 matrices using e.g. setNativeProps, allowing you to just calculate a single transform matrix per element/group you want to transform independently in 3d.
@msand OK I'll do some investigating on it, thanks again, really.
@msand I'm struggling to find out the amount of perspective applied on Android, do you know? on iOS it seems that we have full control over the perspective but not on Android.
@wcandillon There is a matrix decomposition done in the ViewManagers here: https://github.com/react-native-community/react-native-svg/blob/3e3ad13b65ed70f606f6826947cbcfb4f7ce2c4b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java#L211-L379
The logic has been copied from react-native and adapted: https://github.com/facebook/react-native/blob/3b31e69e284074da72108edfb11e41ee74d7100e/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java#L102-L215
There might have been changes in the react-native part which haven't been accounted for in the react-native-svg part, I haven't checked that recently.
You can set breakpoints in android studio and inspect the values there. If you have some sample code that renders differently on ios and android which is representative of the issue, then I'll gladly take a look and see if I can help to isolate and resolve the issue.
It seems this part has changed in react-native: https://github.com/facebook/react-native/blob/f2d58483c2aec689d7065eb68766a5aec7c96e97/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java#L44
private static final float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = (float) Math.sqrt(5);
vs this in react-native-svg: https://github.com/react-native-community/react-native-svg/blob/3e3ad13b65ed70f606f6826947cbcfb4f7ce2c4b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java#L224
I have drawn two rects and use rotateX:70deg to make them look three-dimentional, like the picture below.
Now I want to animate the rects and make them move vertically.
Here is the question, since there is no translateZ for Svg, if I use
{translate:[0, 0, this.state.zAnim]}
inSvg
, it cames out an errorAttempted to assign to readonly property
.Else if I wrapped each rect within a View component and use the
translateY
inView
, the rect behind and be partially overlapped won't respond the onPress event.Here is my code.