Reputation: 1
I'm going to be setting up a flow field in my React app using three-react-fiber (r3f) but before I do that I am using the Line component from the drei library to draw the grid so I can visualize the flow field. The issue that I'm having is trying to get the Lines to be drawn in a grid pattern based on the size of the Canvas it is in and the cell size as well being adaptive to screen resize. I have been using this guide [42:20] (https://youtu.be/MJNy2mdCt20) to help me but the issue comes from the way I adjusted the for loop to draw the grid.
In the original code they are using the base canvas from JavaScript but when I attempted to move the code to the Canvas of r3f the grid is drawing slightly off centered and the grid is not complete.
The Original:
drawGrid(context){
for (let r =0; r<this.rows; c++){
context.beginPath();
context.moveTo(0,this.cellSize * r);
context.lineTo(this.width, this.cellSize * r);
context.stroke();
}
for (let c =0; c<this.cols; c++){
context.beginPath();
context.moveTo(this.cellSize * c,0);
context.lineTo(this.cellSize * c,this.height);
context.stroke();
}
My GridModel component
import { useThree } from "react-three-fiber";
import { Line } from "@react-three/drei";
import { Vector3 } from "three";
import React from "react";
const GridModel = () => {
const { size } = useThree();
const cellSize = 20;
const rows = Math.floor(size.height / cellSize);
const cols = Math.floor(size.width / cellSize);
const rowStart = -(size.height / 2);
const rowEnd = (size.height / 2);
const colStart = -(size.width / 2);
const colEnd = size.width / 2;
const grid = [];
for (let i = 0; i < rows; i++) {
let startPoint = new Vector3(rowStart, i * cellSize - colEnd, 0);
let endPoint = new Vector3(rowEnd, i * cellSize - colEnd, 0);
if(i==0){
console.log(" first row startPoint: " + startPoint.x + ", " + startPoint.y + ", " + startPoint.z + " endPoint: " + endPoint.x + ", " + endPoint.y + ", " + endPoint.z);
}
grid.push(<Line points={[startPoint, endPoint]} color="black" />);
}
for(let i = 0; i < cols; i++) {
let startPoint = new Vector3(i * cellSize + rowStart, colStart, 0);
let endPoint = new Vector3(i * cellSize + rowStart, colEnd, 0);
if(i==0){
console.log(" first col startPoint: " + startPoint.x + ", " + startPoint.y + ", " + startPoint.z + " endPoint: " + endPoint.x + ", " + endPoint.y + ", " + endPoint.z);
}
grid.push(<Line points={[startPoint, endPoint]} color="black" />);
}
return (
<mesh>
{grid}
</mesh>
);
};
export default GridModel;
I updated the for loops to use rowStart, rowEnd, colStart, colEnd instead of 0 and the width/height because in r3f (0,0) is the center of the screen as opposed to how it is the bottom left with the regular JavaScript canvas. To get the bottom left I'm using the useThree hook so that the grid is responsive and the taking half the negative size width and height to get the bottom
The grid drawn on top of my planeGeometry
In the above image the red is the background of my app, the Canvas has a blank background, I'm creating a white plane that is the size of the screen and then I'm creating the Grid (collection of Lines). The issue is that the lines are slightly off centered and they don't complete the grid so it will not be useful in debugging my flow field later on.
I'll also include below the other relevant code for reference
BackgroundViewerPage.js
import React from 'react';
import { Canvas } from '@react-three/fiber';
import BackgroundViewerBackground from '../backgrounds/BackgroundViewerBackground';
import GridModel from '../three/models/GridModel';
import FixedOrthographicCamera from '../three/cameras/FixedOrthographicCamera';
import FixedPerspectiveCamera from '../three/cameras/FixedPerspectiveCamera';
import { OrbitControls, OrthographicCamera } from '@react-three/drei';
import '../../styles/BackgroundViewerPage.css';
const BackgroundViewerPage = () => {
return (
<div className='background-viewer-page-container'>
<Canvas
dpr={window.devicePixelRatio}
>
<OrbitControls />
<FixedOrthographicCamera />
<BackgroundViewerBackground />
<GridModel />
</Canvas>
</div>
);
};
export default BackgroundViewerPage;
BackgroundViewerBackground.js
import React, { useRef } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
const BackgroundViewerBackground = () => {
const { size } = useThree();
return (
<mesh>
<planeGeometry args={[size.width, size.height]} />
<meshBasicMaterial color="white" side={THREE.DoubleSide}/>
</mesh>
);
};
export default BackgroundViewerBackground;
I want the Grid to properly draw Lines on my Canvas with the specified cell size across the entire screen so that I can use it as reference for my flow field.
[edit] After switching height and width in, row and col start and end, the grid draws in the correct spot but the last row and column still has too much space in it.
updated grid with too much space
Upvotes: 0
Views: 792
Reputation: 1
The issue was that I needed to switch size.height with size.width in rowStart and rowEnd, and vice versa for colStart and colEnd because even though the opposites are used to determine the number of rows and cols, the left,right,bottom, and top of the screen should be calculated using the latter.
Additionally the inherent issue with making a grid the size of the screen is that the aspect ratio wont always be 1:1 so square cells dont work. To remedy this im calculating the number of cells that should be on screen horizontally and verticall and then making sure they have the specified cell size by changing resolution. If anyone has a better solution though I'm open to hearing it as I'm not positive this is the precise way but it works for my use case.
import { useThree } from "react-three-fiber";
import { Line } from "@react-three/drei";
import { Vector3 } from "three";
import React from "react";
const GridModel = () => {
const { size } = useThree();
const resolution = 10;
// Calculate the number of rows and columns based on the aspect ratio
const numCells = Math.max(Math.floor(Math.min(size.width, size.height) / resolution), 1);
const cellSize = Math.min(size.width, size.height) / numCells;
const rows = Math.floor(size.height / cellSize);
const cols = Math.floor(size.width / cellSize);
// Calculate adjusted cell sizes to fit the screen
const adjustedCellWidth = size.width / cols;
const adjustedCellHeight = size.height / rows;
const rowStart = -(size.width / 2); //left
const rowEnd = (size.width / 2); //right
const colStart = -(size.height / 2); //bottom
const colEnd = size.height / 2; //top
const grid = [];
for (let i = 0; i < rows; i++) {
let startPoint = new Vector3(rowStart, i * adjustedCellHeight + colStart, 0);
let endPoint = new Vector3(rowEnd, i * adjustedCellHeight + colStart, 0);
grid.push(<Line key={`row-${i}`} points={[startPoint, endPoint]} color="black" />);
}
for (let i = 0; i < cols; i++) {
let startPoint = new Vector3(i * adjustedCellWidth + rowStart, colStart, 0);
let endPoint = new Vector3(i * adjustedCellWidth + rowStart, colEnd, 0);
grid.push(<Line key={`col-${i}`} points={[startPoint, endPoint]} color="black" />);
}
return <mesh>{grid}</mesh>;
};
export default GridModel;
Upvotes: 0