I'm trying to create 3d text using Threejs + react-three/fiber .
I loaded the font using font loader like this :
const font = new FontLoader().parse('/Microsoft Tai Le_Regular.json');
After that I tried to use component inside the mesh , for some reason it didn't work, any other type of Geometry would work .
<mesh>
<textGeometry /> // this can't even be compiled ( maybe it has to do with typescript
</mesh>
With that problem , I tried to create the textGeometry on js instead of jsx So I did this :
const textOptions = {
font: font,
size: props.size,
height: props.height,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 10,
bevelSize: 8,
bevelOffset: 0,
bevelSegments: 5
};
const textGeo = new TextGeometry(props.text, textOptions);
and Passed 'textGeo' to mesh geometry prop
<mesh
geometry={textGeo}
>
still didn't work and gave this error :
can't access property "yMax", data.boundingBox is undefined
Thanks for your help,
So I'll preface this by saying that I'm still a student so I can't explain exactly why all these steps need to be done but here's what worked for me. It seems that your issue is with the path that you are using to parse. The file name itself seems inaccurate but even if the file path is valid, it still will not work, it must be imported. Make sure that your json file is inside of your src folder and import the json file using the relative path.
import myFont from '../relative_path'
Make sure to import extend from r3f
import { extend } from '#react-three/fiber'
Next, textGeometry does not come standard with r3f, so it needs to be imported like so
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
Then extend TextGeometry
extend({ TextGeometry })
This should work to get the textgeometry to compile. Take a look below for the full snippet.
import { extend } from '#react-three/fiber'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
import myFont from '../relative_path'
extend({ TextGeometry })
export default function Text() {
const font = new FontLoader().parse(myFont);
return(
<mesh position={[0,10,0]}>
<textGeometry args={['test', {font, size:5, height: 1}]}/>
<meshLambertMaterial attach='material' color={'gold'}/>
</mesh>
)
}
Obviously you can change the args and material to fit your needs.
Related
I'm using react-tabulator for a component: http://tabulator.info/docs/4.0/frameworks
I have put the component on the page in my app but am struggling to do anything with the styling. Right now, the component just displays everything vertically and looks really bad:
I want to display this horizontally in something that looks like a normal tabular format. I would also like to change column width. I've found limited documentation examples. Someone did ask a similar question and in this StackOverflow thread: How to style react-tabulator table? but I've not been able to edit the styles.css stylesheet to do anything useful.
Here is my component code:
import React from 'react'
import { ReactTabulator } from 'react-tabulator'
import 'react-tabulator/lib/styles.css';
const TabularData = (props) => {
const dataArray = []
//gets just first streelights record
for (const [i, v] of props.streetlights.features.entries()) {
if (i < 1) {
dataArray.push(v.properties); // properties of each item is what contains the info about each streetlight
}
}
let columns = [
{title:"WORKLOCATI", field:"WORKLOCATI"},
{title:"WORKREQUES", field:"WORKREQUES"},
{title:"WORK_EFFEC", field:"WORK_EFFEC"},
{title:"WORK_REQUE", field:"WORK_REQUE"},
]
return (
<ReactTabulator
columns={columns}
layout={"fitData"}
data={dataArray}
/>
)
}
export default TabularData
The css in react-tabulator/lib/styles.css is just the most base-level css.
Try importing one of the pre-built themes:
import "react-tabulator/css/bootstrap/tabulator_bootstrap.min.css";
There are a whole bunch of them in the css folder, and you can use them as a basis for creating your own.
Minimum working example here.
To get the right styling you will also have to import tabulator.min.css in your module, which is the theme, according to here.
Your imports should look like this:
import { ReactTabulator } from 'react-tabulator'
import 'react-tabulator/lib/styles.css';
import 'react-tabulator/lib/css/tabulator.min.css'; // theme
Without it, it looks like the image you posted:
With it, it looks like this:
In the folder node_modules/react-tabulator/css you can find more themes.
Currently I'm using Next.js with Next-i18next for I18N, but I understand that the React/i18next implementation is basically the same.
The problem I'm having is that I need to interpolate a next Link component inside some translation text, but depending on the language (English vs German), the order of the text and the link would change.
For instance the text I'm struggling with is: 'Accept data policy' vs 'Datenschutzerklärung akzeptieren'
As of the moment I have a quick fix by creating two values in the translation JSON files for the text and the link and then swapping the position based on the current language. Obviously this is not a sustainable solution. I have tried to utilise the 'Trans' component but this is showing some unexpected behaviour where the translation only kicks in after the page is refreshed, otherwise you see the text inside the Trans component.
example:
function LinkText({ href, children}) {
return <Link to={href || ''}>{children}</Link>;
}
return (
<Trans i18nKey="sentence">
text before link
<LinkText href="/data-policy">{t("dataPolicy")}</LinkText>
text after link
</Trans>
);
and the JSON in question:
{
"sentence": "agree to our <1><0/></1>",
"dataPolicy": "data policy"
}
Here's a link to CodeSandbox I made to replicate the problem with in React: link
(P.S The implementation of i18next doesn't seem to effectively swap out the languages in Codesandbox at the moment, but I included it as the code is there for a MWE)
Thanks in advance for your help, this has been driving me insane.
You had few missing parts,
Your i18next config was lack of a way to fetch the locale files, I've added i18next-http-backend.
You should use Trans component to inject the link to the sentence.
Your locale json should look like this:
{
"sentence": "Accept <0>data policy</0>"
}
// TranslatedLink.js
import React from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { Link } from 'react-router-dom';
function LinkText({ href, children }) {
return <Link to={href || ''}>{children}</Link>;
}
export default function TranslatedLink() {
const { t } = useTranslation(['common']);
return (
<div style={{ padding: 50 }}>
<Trans i18nKey="sentence" t={t} components={[<LinkText href="/data-policy" />]} />
</div>
);
}
A working example: https://codesandbox.io/s/react-i18n-interpolation-issue-forked-ck8l4
In my react app, I am trying to make a view page which shows 3d-mesh exported from pix4d. This mesh consists of three types of files, (.obj, .mtl, .jpg) pix4d.com.
I am new, with react-three-fiber, which I suppose is best way to achieve my solution to my problem.
Below is whole code of react component used to load and render 3D-Model.
code
Thanks in Advance!
I want to understand how to attach texture & material to my obj rendered model.
I was looking for this answer for a couple of weeks, finally I've found a way to make it work.
Material loader is not resolving by itself the import of remote files (it does on web, not in mobile, maybe in the future it will). So, I'm creating material and assigning it images by hand.
Something like this:
import { TextureLoader } from 'expo-three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
// useThree is used to get scene reference
import { useThree } from 'react-three-fiber';
const textureLoader = new TextureLoader();
const mapImage = textureLoader.load(require('path/to/image1.png'))
const normalMapImage = textureLoader.load(require('path/to/image2.png'))
Note that TextureLoader from expo-three can handle the file resource returned from require()
const loaderObj = new OBJLoader();
const loaderMtl = new MTLLoader();
export default props => {
const { scene } = useThree();
loaderMtl.load(
'https://url_to/material.mtl',
mtl => {
mtl.preload();
loaderObj.setMaterials(mtl);
loaderObj.load(
'https://url_to/model.obj',
obj => {
// simple logic for an obj with single child
obj.children[0].material.map = mapImage;
obj.children[0].material.normalMap = normalMapImage;
scene.add(obj)
}
)
}
)
return null;
}
This is my first successful attempt to render an obj with mtl including a map and a normal map, so since it works, we can keep updating the code for improvements.
Another way to load model with texture is by specifying the path where your texture has been stored. In one case, mtl + obj + texture files are stored in your react project's directory 'public/models/'. So you can specify the path by calling setPath() function prior loading your material or object file and it should load your texture on the material. You may also want to make sure that in ./mtl file the texture name is correct. It should be called following by map_Kd in .mtl file.
const Model = () => {
const materialLoader = new MTLLoader().setPath('./model/').load(MaterialFile);
const objLoader = new OBJLoader().setMaterials(materialLoader).load(OojectFile);
return <primitive object={objLoader} />;
};
I have read several posts about issues that people are having with React Native and the require() function when trying to require a dynamic resource such as:
Dynamic (fails):
urlName = "sampleData.json";
data = require('../' + urlName);
vs. Static (succeeds):
data = require('../sampleData.json');
I have read on some threads that this is a bug in React Native and in others that this is a feature.
Is there a new way to require a dynamic resource within a function?
Related Posts (all fairly old in React time):
Importing Text from local json file in React native
React Native - Dynamically List/Require Files In Directory
React Native - Image Require Module using Dynamic Names
React Native: how to use require(path) with dynamic urls?
As i have heard of, react's require() only uses static url not variables, that means that you have to do require('/path/file'), take a look at this issue on github and this one for more alternative solutions, there are a couple of other ways to do it!
for e.g
const images = {
profile: {
profile: require('./profile/profile.png'),
comments: require('./profile/comments.png'),
},
image1: require('./image1.jpg'),
image2: require('./image2.jpg'),
};
export default images;
then
import Images from './img/index';
render() {
<Image source={Images.profile.comments} />
}
from this answer
Here is my solution.
Setup
File structure:
app
|--src
|--assets
|--images
|--logos
|--small_kl_logo.png
|--small_a1_logo.png
|--small_kc_logo.png
|--small_nv_logo.png
|--small_other_logo.png
|--index.js
|--SearchableList.js
In index.js, I have this:
const images = {
logos: {
kl: require('./logos/small_kl_logo.png'),
a1: require('./logos/small_a1_logo.png'),
kc: require('./logos/small_kc_logo.png'),
nv: require('./logos/small_nv_logo.png'),
other: require('./logos/small_other_logo.png'),
}
};
export default images;
In my SearchableList.js component, I then imported the Images component like this:
import Images from './assets/images';
I then created a new function imageSelect in my component:
imageSelect = network => {
if (network === null) {
return Images.logos.other;
}
const networkArray = {
'KL': Images.logos.kl,
'A1': Images.logos.a1,
'KC': Images.logos.kc,
'NV': Images.logos.nv,
'Other': Images.logos.other,
};
return networkArray[network];
};
Then in my components render function I call this new imageSelect function to dynamically assign the desired Image based on the value in the this.state.network:
render() {
<Image source={this.imageSelect(this.state.network)} />
}
The value passed into the imageSelect function could be any dynamic string. I just chose to have it set in the state first and then passed in.
I hope this answer helps. :)
For anyone reading this that cannot work with the existing answers, I have an alternative.
First I'll explain my scenario. We have a mono repo with a number of packages (large react-native app). I want to dynamically import a bunch of locale files for i18n without having to keep a central registry in some magic file. There could be a number of teams working in the same monorepo and the DX we want is for package developers to be able to just add their local files in a known folder {{packageName}}/locales/en.json and have our core i18n functionality pick up their strings.
After several less than ideal solutions, I finally landed on https://github.com/kentcdodds/babel-plugin-preval as an ideal solution for us. This is how I did it:
const packageEnFiles = preval`
const fs = require('fs');
const path = require('path');
const paths = [];
const pathToPackages = path.join(__dirname, '../../../../packages/');
fs.readdirSync(pathToPackages)
.filter(name => fs.lstatSync(path.join(pathToPackages, name)).isDirectory())
.forEach(dir => {
if (fs.readdirSync(path.join(pathToPackages, dir)).find(name => name === 'locales')) {
const rawContents = fs.readFileSync(path.join(pathToPackages, dir, 'locales/en.json'), 'utf8');
paths.push({
name: dir,
contents: JSON.parse(rawContents),
});
}
});
module.exports = paths;
`;
Then I can just iterate over this list and add the local files to i18next:
packageEnFiles.forEach(file => {
i18n.addResourceBundle('en', file.name, file.contents);
});
If you need to switch between multiple locally stored images, you can also use this way:
var titleImg;
var textColor;
switch (this.props.data.title) {
case 'Футбол':
titleImg = require('../res/soccer.png');
textColor = '#76a963';
break;
case 'Баскетбол':
titleImg = require('../res/basketball.png');
textColor = '#d47b19';
break;
case 'Хоккей':
titleImg = require('../res/hockey.png');
textColor = '#3381d0';
break;
case 'Теннис':
titleImg = require('../res/tennis.png');
textColor = '#d6b031';
break;
}
In this snippet I change variables titleImg and textColor depending of the prop. I have put this snippet directly in render() method.
I have found that a dynamic path for require() works when it starts with a static string. For example require("./" + path) works, whereas require(path) doesn't.
Simple to dynamic images (using require)
Example array(into state)
this.state={
newimage: require('../../../src/assets/group/kids_room.png'),
randomImages=[
{
image:require('../../../src/assets/group/kids_room.png')
},
{
image:require('../../../src/assets/group/kids_room2.png')
}
,
{
image:require('../../../src/assets/group/kids_room3.png')
}
]
}
Trigger image( like when press button(i select image random number betwenn 0-2))
let setImage=>(){
this.setState({newimage:this.state.randomImages[Math.floor(Math.random() * 3)];
})
}
view
<Image
style={{ width: 30, height: 30 ,zIndex: 500 }}
source={this.state.newimage}
/>
Hey lads I rounded another way to require It's ugly but works. Images dynamically. Instead of storing your URL in the state you store the entire JSX. For an example:
state = {
image: []
};
Instead of
let imageURL = `'../assets/myImage.png'`
this.state.image = imageURL
You use
let greatImage = (<Image source={require(../assets/myImage.png)}></Image>)
this.state.image = greatImage
To render in the JSX
{this.state.image}
You can style your image in the variable too. I had to use some if statements to render some images dynamically and after break my head for 2 hours this was the way that solved my problem. Like I said It's ugly and probably wrong.
Are you using a module bundler like webpack?
If so, you can try require.ensure()
See: https://webpack.js.org/guides/code-splitting/#dynamic-imports
Reading through the docs, I've found a working answer and I'm able to use dynamic images, in the docs they refer to it as Network Images here
https://facebook.github.io/react-native/docs/images#network-images
Not sure if this can be applied to other file types, but as they list require with non image types
You would need to use the uri: call
data = {uri: urlName}
For me I got images working dynamically with this
<Image source={{uri: image}} />
Try the solution mentioned in this thread for Android. This solves the issue but unfortunately, it's only for android.
But make sure to run react-native run-android after every update. Else, the added images won't appear in the app.
This seems to work :
const {
messages,
} = require(`../${dynamicPath}/messages.specific`);
I'm trying to build a simple object-viewer in React with Meteor that can import .obj and .mtl Files using the following npm modules:
three(0.87.1)
react(15.6.1)
three-obj-loader(1.1.3)
three-mtl-loader(1.0.1)
So far i have managed to display an object using the OBJLoader.
But when i try to render an object after applying a texture with MTLLoader, i get this error from console:
Uncaught TypeError: Cannot read property 'toString' of undefined
at WebGLPrograms.getProgramCode (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:50707)
at initMaterial (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54628)
at setProgram (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54820)
at WebGLRenderer.renderBufferDirect (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:53883)
at renderObject (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54613)
at renderObjects (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54586)
at WebGLRenderer.render (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54350)
at WebGlDisplay.renderScene (WebGlDisplay.jsx:86)
at onClick (WebGlDisplay.jsx:90)
at HTMLUnknownElement.boundFunc (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:8794)
Cause: material.onBeforeCompile in getProgramCode is undefined
My code looks like this:
import React, { Component } from 'react'
import THREE from 'three'
const MTLLoader = require('three-mtl-loader');
const OBJLoader = require('three-obj-loader')(THREE);
export default class WebGlDisplay extends Component {
constructor(props) {
super(props)
}
//init canvas
init(){
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setClearColor('#000000', 0.2);
renderer.setSize(width, height);
camera.position.set(3,4,6);
camera.lookAt(new THREE.Vector3());
this.scene = scene;
this.camera = camera;
this.renderer = renderer;
this.mount.appendChild(this.renderer.domElement);
}
//load & render object
drawOBJ(){
const mtlLoader = new MTLLoader();
let onProgress = function(e){console.log("rendering:" + e)};
let onError = function(e){console.log("error:" + e)};
mtlLoader.load("eagle.mtl", materials => {
materials.preload();
// OBJ Loader
const objLoader = new THREE.OBJLoader();
this.materials = materials;
objLoader.setMaterials(materials);
objLoader.load("eagle.obj", object => {
this.object = object;
this.scene.add(object);
}, onProgress, onError);
}, onProgress,onError);
this.renderScene();
}
componentDidMount() {
this.init();
this.drawOBJ();
}
renderScene() {
this.renderer.render(this.scene, this.camera)
}
render() {
return (
<div onClick={(e) => this.renderScene()}
style={{ width: '800px', height: '600px' }}
ref={(mount) => { this.mount = mount }}
/>
)
}
}
Does anyone have an idea why i get this error?
I've tried to use different .obj- and .mtl-files, but the error remains (whenever i try to call renderScene()).
By any chance, could it be a problem with the module versions, or maybe some timing problems while loading?
Any help would be appreciated.
The problem seems to be that the three-mtl-loader NPM package is referencing an outdated three.js version in it's package.json, so even though you are using the latest version of three, the plugin isn't!
Obviously this isn't a viable long-term fix, but I changed the version for three in node_modules/three-mtl-loader/package.json to 0.87.1 and deleted the directory node_modules/three-mtl-loader/node_modules for good measure and ran my example and it worked straight away, textures and all.
Clearly the plugin needs to be updated. I also saw at least one functional difference between the source in the plugin and in the three examples folder ('Tr' vs 'tr' in a case statement), and it doesn't follow the same initialization behavior as other loader plugins (specifically it isn't initialized by calling require("three-mtl-loader")(THREE)), so there's a bit of work to get it ship-shape.
Alternately, it appears that the author has updated the version number to 0.86.0 in their repo (which is high enough), just hasn't done a deploy to NPM. So, if you feel brave, you can just change your package.json to have the line
"dependencies": {
...
"three-mtl-loader": "git+https://git#github.com/nascherman/three-mtl-loader.git",
...
}
As a workaround, what i ended up doing is to get a local copy of the newest MTLLoader Version and slightly modifing it, as it seems to be a problem with the version pointed out by #user1691694.
In case somebody needs this here my way of importing it:
In the MTLLoader file, add the following line at the top:
import THREE from 'three';
and at the bottom:
module.exports.default = THREE.MTLLoader
Use it like in the drawOBJ function in the question post and import it in the target file like this:
import MTLLoader from './MTLLoader.js';