Using lookAt in #react-three/fiber - reactjs

I am trying learn how to use three js with react.For this purpose I am using #react-three/fiber and #react-three/drei.
This project by chriscourses was what I used to learn three js and I hope to use it to learn using threejs with react.
The below code should show boxes sprouting out of the cordinates given however currently they are all skewed towards one direction. Using lookat should solve the problem, however I do not know how to use lookat in #react-three/fiber / #react-three/drei.
import React, { FC, useRef } from 'react';
import { Box as NativeBox } from '#react-three/drei'
import { Vector3 } from 'three';
import { useFrame } from '#react-three/fiber';
type Props = {
country: any,
}
const lookAtCubePosition = new Vector3(0, 0, 0)
const Box: FC<Props> = (props) => {
const mesh = useRef()
const { country } = props;
const scale = country.population / 1000000000
const lat = country.latlng[0]
const lng = country.latlng[1]
const zScale = 0.8 * scale
const latitude = (lat / 180) * Math.PI
const longitude = (lng / 180) * Math.PI
const radius = 5
const x = radius * Math.cos(latitude) * Math.sin(longitude)
const y = radius * Math.sin(latitude)
const z = radius * Math.cos(latitude) * Math.cos(longitude)
console.log('box');
const lookAtFunc = (lookAt: Vector3) => {
lookAt.x = 0;
lookAt.y = 0;
lookAt.z = 0;
}
useFrame((state) => {
state.camera.lookAt(lookAtCubePosition)
})
return <NativeBox
args={[
Math.max(0.1, 0.2 * scale),
Math.max(0.1, 0.2 * scale),
Math.max(zScale, 0.4 * Math.random())
]}
position={[
x, y, z
]}
lookAt={lookAtFunc}
ref={mesh}
>
<meshBasicMaterial
attach="material"
color="#3BF7FF"
opacity={0.6}
transparent={true}
/>
</NativeBox>;
};
export default Box;

If you are using OrbitControls, you must set the 'target' for OrbitControls rather than lookAt of the Camera since the lookAt of the camera is being overridden by OrbitControls

I think (the documentation is not too specific on that) that we are not meant to set lookAt as a prop but instead use it as a function. We can gain access to the active camera using the useThree hook.
In a component which is mounted as a child of Canvas do:
import { useThree } from "#react-three/fiber";
// Takes a lookAt position and adjusts the camera accordingly
const SetupComponent = (
{ lookAtPosition }: { lookAtPosition: { x: number, y: number, z: number } }
) => {
// "Hook into" camera and set the lookAt position
const { camera } = useThree();
camera.lookAt(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
// Return an empty fragment
return <></>;
}

Related

Animation in Canvas with React plays in an instant then vanishes

I have a 320x40 .png file that I am trying to play each animation from inside of my canvas, for a total of 10 frames. Each individual animation frame is a 32x40 section of the png file. I'll cut to the chase and show the png animation code for the character:
export default function SpriteAni(ctx, charAnim, currentFrame) {
const charSprite = new Image();
charSprite.src = `./images/char_${charAnim}.png`
const posx = 320;
const posy = 200;
const sheet_width = 32;
const sheet_height = 40;
const sprite_width = sheet_width * 1.5;
const sprite_height = sheet_height * 1.5;
ctx.drawImage(charSprite, currentFrame * sheet_width, 0, sheet_width, sheet_height, posx, posy, sprite_width * 1.5, sprite_height * 1.5);
}
Here's the relevant code for drawing this to the screen inside of my main Canvas.jsx file
import { useEffect, useRef, useState } from "react";
import SpriteAni from '../SpriteAni.jsx';
// Canvas and game logic for display
const Canvas = ({ width, height }) => {
const canvasRef = useRef(null);
const requestIdRef = useRef(null);
let frame = 0; // frames canvas is drawn to screen
const [currentFrame, setCurrentFrame] = useState(0); // used to determine current frame for current Wario animation
const [charAnim, setCharAnim] = useState('walk')
// Char animation frame counts
const walk_frames = 9;
const drawFrame = () => {
const ctx = canvasRef.current.getContext('2d');
/// clear screen each frame
ctx.clearRect(0,0,constants.CANVAS_WIDTH,constants.CANVAS_HEIGHT);
// draw graphics
handleBackground(ctx);
SpriteAni(ctx, charAnim, currentFrame);
if (currentFrame >= walk_frames) setCurrentFrame(0);
else setCurrentFrame(currentFrame + 1);
}
const tick = () => {
if (!canvasRef.current) return;
drawFrame();
frame++;
requestAnimationFrame(tick)
}
useEffect(() => {
requestIdRef.current = requestAnimationFrame(tick)
return () => {
cancelAnimationFrame(requestIdRef.current)
};
}, [])
return (
<canvas
width={width}
height={height}
style={canvasStyle}
ref={canvasRef}
/>
)
}
export default Canvas
const canvasStyle = {
border: "1px solid black",
position: "absolute",
}
What I'm especially confused about is I can delete all of the "if (currentFrame >= ${charAnim}_frames)..." code underneath SpriteAni in the drawFrame function and nothing changes. It seems to all be acting independently of any of that and just races through all 10 frames before vanishing off of the screen within one second. I don't even understand how that's possible since the only function inside of SpriteAni is a drawImage of which the only variable that should be updating each frame is currentFrame.
How is it animating anything independently like that? I'm just baffled what's going on here! Any help greatly appreciated!

React Three Fiber not appearing as background in mobile browsers

I'm attempting to use a design built with React-Three-Fiber as the background of my application.
The canvas appears to show and run the animation on chrome and firefox dev tools in the desktop browsers but is failing to show on mobile. I've tried firefox, chrome, and safari using my iPhone and a few other phones but still no prevail.
It is a fairly basic animation so it shouldn't be too much for the browser to handle.
I've included the proper meta tag in the index.html, found within the documentation.
The code below that is contained within its own component.
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
import * as THREE from "three";
import React, { Suspense, useMemo, useRef } from "react";
import styled from "styled-components";
import { Canvas, useFrame } from "#react-three/fiber";
import vertexShader from "./Shaders/VertexShader";
import fragmentShader from "./Shaders/FragmentShader";
export default function Animation() {
// universal points ref
const points = useRef();
const radius = 2;
const Planet = () => {
const distance = 2;
const count = 20000;
const particlesPosition = useMemo(() => {
// create the float32 array to store the point positions.
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = THREE.MathUtils.randFloatSpread(360);
const phi = THREE.MathUtils.randFloatSpread(360);
// Generate random values for x, y, z
let x = distance * Math.sin(theta) * Math.cos(phi);
let y = distance * Math.sin(theta) * Math.sin(phi);
let z = distance * Math.cos(theta);
// add each value to the array
positions.set([x, y, z], i * 3);
}
return positions;
}, [count]);
const uniforms = useMemo(
() => ({
uTime: {
value: 0.8,
},
uRadius: {
value: radius,
},
}),
[]
);
useFrame((state) => {
const { clock } = state;
// adding 100 starts the animation at 100s ahead of animation start time.
points.current.material.uniforms.uTime.value = clock.elapsedTime + 110;
});
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute // bufferAttribute will allow you to set the position attribute of the geometry.
attach="attributes-position" // this will allow the data being fed to bufferAttribute to be accessable under the position attribute.
count={particlesPosition.length / 3} //number of particles
array={particlesPosition}
itemSize={3} // number of values from the particlesPosition array associated with one item/vertex. It is set to 3 becuase position is based on X, Y, Z
/>
</bufferGeometry>
<shaderMaterial
depthWrite={false}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={uniforms}
/>
</points>
);
};
return (
//Canvas takes place of all the boilerplate to get three.js running with react.
<Wrapper>
<Canvas className="canvas">
<ambientLight intensity={0.5} />
<Suspense>
<Planet />
</Suspense>
</Canvas>
</Wrapper>
);
}
const Wrapper = styled.div`
position: relative;
background: inherit;
height: 100vh;
width: 100vw;
`;

Chart in d3js not showing up in react app

I am trying to follow the tutorial here but using react app. The chart is not showing up and I am not sure what I am doing wrong, please help. I am using d3 v7 and react 17. I get no errors but the buttons show up so the component is called correctly.
This is my component:
import * as d3 from 'd3'
import * as React from 'react'
import { useState, useEffect} from 'react'
const PieChart = (props) => {
const ref = React.createRef()
useEffect(() => {
draw()
});
// set the dimensions and margins of the graph
const width = 450, height = 450, margin = 40;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
const radius = Math.min(width, height) / 2 - margin;
// set the color scale
const color = d3.scaleOrdinal()
.domain(["a", "b", "c", "d", "e", "f"])
.range(d3.schemeDark2);
// create 2 data_set
const data1 = {a: 9, b: 20, c:30, d:8, e:12}
const data2 = {a: 6, b: 16, c:20, d:14, e:19, f:12}
const svg = d3.select(".PieChart")
// A function that create / update the plot for a given variable:
const update = (data) => {
if (data == "data1"){
data = data1;
}else{
data = data2;
}
// Compute the position of each group on the pie:
const pie = d3.pie()
.value(function(d) {return d[1]; })
.sort(function(a, b) { return d3.ascending(a.key, b.key);} ) // This make sure that group order remains the same in the pie chart
const data_ready = pie(Object.entries(data))
// map to data
const u = svg.selectAll("path")
.data(data_ready)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
u
.join('path')
.transition()
.duration(1000)
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(radius)
)
.attr('fill', function(d){ return(color(d.data[0])) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1)
}
const draw = () => {
svg.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${width/2}, ${height/2})`);
// Initialize the plot with the first dataset
update("data1")
};
return <div ref={ref} >
<button onClick={update("data1")}>Data 1</button>
<button onClick={update("data2")}>Data 2</button>
<div className="PieChart" />
</div>
}
export default PieChart
This is what I get, no graph
I think there are quite a few things that need to be fixed.
First & foremost is the usage of ref. You have to do const ref = React.useRef() i.e use useRef instead of createRef.
Also, the ref has to be set to the svg element and not the div.
The 'almost' working code is here. https://codesandbox.io/s/flamboyant-heisenberg-vppny?file=/src/pie.js
I say almost because the chart does not render on page load! I do not know why (I am not very familiar with react). To render the chart, change the value of width or height from 450 to 451. I do not know why this works but it does.
One the page is loaded with the chart, the buttons & the chart transition work fine.
If you figure how to render the chart on page load, please let me know.

Is there a way to access a value computed using props in componentDidMount?

I'm working on converting a vanilla js gsap(GreenSock) animation to react, and want to access values based on props in componentDidMount(where gsap tweens are called). The animation is based on https://codepen.io/zadvorsky/pen/dILAG, using Delaunay triangulation and tweens to explode a shattered image. How do I access said values?
Originally I had the calculations contained in if(shatter) inside render but the values of rx, ry, and delay were undefined in the tween used in componentDidMount. I moved said calculations to componentDidMount so said values would be available, but the props are not available then, even with the use of a constructor.
export class Explosion extends React.Component {
constructor(props) {
super(props)
this.state = {
shatter: false
}
}
tl0 = new TimelineMax();
tl1 = new TimelineMax();
fragmentNode = React.createRef()
containerNode = React.createRef()
componentDidMount() {
if (this.state.shatter) {
const triangulatedValues = triangulate({
this.props.width,
this.props.height,
this.props.startX,
this.props.startY
})
const {
vertices,
indices
} = triangulatedValues
const ExplosionFragments = []
for (let i = 0; i < indices.length; i += 3) {
let point0 = vertices[indices[i + 0]]
let point1 = vertices[indices[i + 1]]
let point2 = vertices[indices[i + 2]]
let fragmentCentroid = computeCentroid({
point0,
point1,
point2
})
let dx = fragmentCentroid[0] - startX
let dy = fragmentCentroid[1] - startY
let d = Math.sqrt(dx * dx + dy * dy)
let rx = -30 * sign(dy)
let ry = -90 * -sign(dx)
let delay = d * 0.003 * randomRange(0.7, 1.1)
let box = computeBoundingBox({
point0,
point1,
point2
})
}
}
this.tl1
.to(this.fragmentNode, 1, {
z:500,
rotationX:rx,
rotationY:ry,
ease:Power2.easeInOut
})
.to(this.fragmentNode, 0.4,{alpha:0}, 1)
this.tl0
.insert(this.tl1, delay)
}
render() {
const {
shatter
} = this.state
return (
<Container
ref={this.containerNode}
onClick={this.handleToggleShatter}
>
{shatter ? (
<div>
<OverlayImage
src="http://i67.tinypic.com/2eq9utk.jpg"
/>
<canvas
width={width}
height={height}
ref={this.fragmentRef}>
</canvas>
</div>
) : (
<OverlayImage
src="http://i67.tinypic.com/2eq9utk.jpg"
/>
)}
</Container>
)
}
}
export default Explosion
https://codesandbox.io/s/ll139x3nx7
I expect the props to be available in componentDidMount, but the error "Parsing error: this is a reserved word" gets thrown. This is just an error for a possible solution, and I'm open to any way forward.
When you do:
const triangulatedValues = triangulate({
this.props.width,
this.props.height,
this.props.startX,
this.props.startY
})
You're passing in an Object where the keys are this.props.width etc and the values are also this.props.width because you haven't specified any other key, so it'll try to call the key the name of the variable.
Try:
const triangulatedValues = triangulate({
width: this.props.width,
height: this.props.height,
startX: this.props.startX,
startY: this.props.startY
})

World Map Zoom with D3 + ReactJS

I found following code for zoom in a simple D3 solution which works well with Javascript:
var zoom = d3.behavior.zoom()
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale);
feature.attr("d", path);
circle.attr("transform", ctr);
})
;
zoom.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([h / 6, h])
;
When I try to convert this code in D3 + ReactJS, I get issues. There have been many since I've tried so many different solutions. The latest one zooms my world map once but it's all a mess.
This is my code:
import React, { Component } from 'react';
import 'd3';
import * as d3Z from 'd3-zoom'
import * as d3 from 'd3-selection';
//import d3Scale from 'd3-scale'
import { geoMercator, geoPath } from 'd3-geo';
import './WorldMap.css';
import countries from '../resources/world-countries.json';
//
export default class WorldMap extends Component {
componentDidUpdate() {
const width = 1300, //document.body.clientWidth,
height = 600;//document.body.clientHeight;
const circleRadius = 2;
const lstRadius = [7, circleRadius];
const lstColor = ["green", "white"];
const duration = 500;
const lstShots = [];
const projection = geoMercator();
const path = geoPath()
.projection(projection);
const d3Zoom = d3Z.zoom();
for (var i = 0; i < this.props.data.length; i++) {
lstShots.push(JSON.parse(JSON.stringify(this.props.data[i])));
}
const getCirclePos = (d) => {
return "translate(" + projection([d.long, d.lat]) + ")";
}
const svgObj = d3.select(this.refs.svgMap);
const feature = svgObj
.selectAll("path.feature")
.data(countries.features)
.enter().append("path")
.attr("class", "feature");
projection
.scale(width / 6.5)
.translate([width / 2, height / 1.6]);
const zoom = d3Zoom
.on("zoom", function() {
console.log(d3.event);
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.scale);
feature.attr("d", path);
// circle.attr("transform", getCirclePos);
})
;
svgObj.call(zoom);
// console.log(zoom);
// zoom.translateTo(projection.translate())
// .scale(projection.scale())
// .scaleExtent([height / 6, height])
// ;
feature.attr("d", path);
// eslint-disable-next-line
const circle = svgObj.selectAll("circle")
.data(lstShots)
.enter()
.append("circle")
.attr("r", circleRadius)
.attr("fill", 'white')
.attr("transform", getCirclePos)
.attr("node", function(d) { return JSON.stringify(d); })
.style("cursor", "pointer");
}
render() {
return (
<svg ref="svgMap"></svg>
);
}
}
The problem is that d3.event does not contain fields of translate or scale which I have seen people use in other example of d3-zoom. While I have replaced translate with transform in zoom function, I don't know what to replace d3.event.scale with.
If you port code from d3v3 to d3v4 you need to do it all, d3.event.scale does not exist in d3v4
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.transform.k);
You need to initialise the zoom too
svgObj.call(zoom);
svgObj.call(zoom.transform, d3.zoomIdentity.translate(projection.translate()[0], projection.translate()[1]).scale(projection.scale()));
Don't use ref strings but Callback Refs
I needed to use componentDidMount() to see anything.

Resources