I am building an application on react where I have to show a 3D model (glTF) with an animation.
I am using "three-gltf-loader" npm package and I am able to load the 3D model correctly. But while playing the animation it's not working fine. As soon as I add the code to initiate the animation the model is becoming distorted.
I tried searching the solution over google, but its not working out for me. I am using the basic code base from this URL "https://medium.com/#colesayershapiro/using-three-js-in-react-6cb71e87bdf4".
The following code is on the function "componentDidMount()"
let clock = new THREE.Clock();
const loader = new GLTFLoader();
loader.load(
objMap,
( gltf ) => {
const model = gltf.scene;
// called when the resource is loaded
model.traverse( function ( child ) {
if ( child.isMesh ) {
child.geometry.center(); // center here
}
});
model.scale.set(100,100,100);
if (gltf.animations && gltf.animations.length) {
const mixer = new THREE.AnimationMixer(model);
mixers.push( mixer );
for (var i = 0; i < gltf.animations.length; i++) {
var animation = gltf.animations[3];
if (animation.name == 'Idle'){
mixer.clipAction(animation).play();
}
}
}
scene.add( model );
},
( xhr ) => {
// called while loading is progressing
console.log( `${( xhr.loaded / xhr.total * 100 )}% loaded` );
},
( error ) => {
// called when loading has errors
console.error( 'An error happened', error );
},
);
The following code is on the function "animate()"
for ( let mixer of this.mixers ) {
mixer.update( delta );
}
...
Related
I am using camera package in order to take images from my phone. Now I want to convert those images to one single pdf file by clicking on a button. For that I am using pdf package. The problem is, when I take an image/images and click on the button I am always getting this exception:
Exception: This widget created more than 20 pages. This may be an issue in the widget or the document
I have read many suggestions here: #145, #146 and here stack but nothing helped in my case.
Only case where I have managed to create a pdf was when I cropped the images to a really small size. FYI this is what I have used for cropping image_cropper
Here are the code snippets that are in charge for adding images to pdf and saving them.
Button:
Expanded(
child: RaisedButton(
color: _appTheme.blueGrey,
onPressed: () async {
await _addImagesToPdf();
final _filePath = await _savePdf();
model.navigateToUploadImagePage(_filePath);
},
child: Text(model.nextOrUploadLabel.toUpperCase(),
style: Theme.of(context).textTheme.headline1),
),
),
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
//PdfImage _image;
final _pdf = pw.Document();
final _image = <PdfImage>[];
final _imageFit = <pw.BoxFit>[];
Future<void> _addImagesToPdf() async {
// for (var i = 0; i < _model.imageFilePaths.length; i++) {
// _image = PdfImage.file(
// _pdf.document,
// bytes: File(_model.imageFilePaths[i]).readAsBytesSync(),
// );
// }
//trying with list of pdf images => Best solution for now. It uploads
//multiple images but only when they are cropped by a lot
for (var i = 0; i < _model.imageFilePaths.length; i++) {
_image.add(
PdfImage.file(
_pdf.document,
bytes: File(_model.imageFilePaths[i]).readAsBytesSync(),
),
);
_imageFit.add(pw.BoxFit.values[i]);
}
_pdf.addPage(
pw.MultiPage(
// maxPages: _model.imageFilePaths.length,
pageFormat: PdfPageFormat.a4,
//margin: const pw.EdgeInsets.all(32),
build: (context) => <pw.Widget>[
//If you wrap everything inside a column, the framework cannot
//create new pages: a Column is seen as one object
//that cannot be split. You can try Wrap instead.(Comment from author)
pw.Wrap(
children: <pw.Widget>[
for (var i = 0; i < _image.length; i++)
pw.ClipRect(
child: pw.Image(
_image[i],
fit: _imageFit[i],
),
),
],
),
],
),
);
}
Future<String> _savePdf() async {
final tempDir = await getTemporaryDirectory();
_file = File('${tempDir.path}/${DateTime.now()}.pdf')
..writeAsBytesSync(_pdf.save());
return _file.path;
}
UPDATE:
It's working now and this is the code if someone is having same troubles as I did.
P.S. Update your pdf package to the latest version(1.13 in my case)
final _image = <pw.Image>[];
Future<void> _addImagesToPdf() async {
for (final imgPath in _model.imageFilePaths) {
final bytes = File(imgPath).readAsBytesSync();
_image.add(pw.Image.provider(
pw.MemoryImage(bytes),
));
}
for (final img in _image) {
_pdf.addPage(
pw.Page(
pageFormat: PdfPageFormat.a4,
build: (context) => pw.Center(
child: pw.Container(child: img),
),
),
);
}
}
I'm trying to change the material of my imported FBX-file. I can easliy change attributes of the material, that is already attached to my FBX file, but I can't change the material to my predefined "matAluMedium". I did this before in another project, but can't figure out, what I did wrong this time.
Hope you can help
init();
function init() {
const cubeTexureloader = new CubeTextureLoader();
envMap = cubeTexureloader.load([
"assets/models/textures/envMap/px.jpg",
"assets/models/textures/envMap/nx.jpg",
"assets/models/textures/envMap/py.jpg",
"assets/models/textures/envMap/ny.jpg",
"assets/models/textures/envMap/pz.jpg",
"assets/models/textures/envMap/nz.jpg",
]);
matAluMedium = new MeshStandardMaterial({
color: 0x98720b,
roughness: 0.2,
metalness: 1,
envMap: envMap,
});
}
function newFBX(props) {
const fbx = useLoader(FBXLoader, "assets/models/" + props.path + ".fbx");
fbx.traverse( function ( child ) {
if ( child instanceof Mesh ) {
child.material = matAluMedium;
}
} );
return
(<mesh>
<primitive object={fbx} dispose={null} />
</mesh>)
};
So I did a workaround to solve my problem. I don't know why, but when I replace "traverse" with "foreach" it works. However...?
Maybe someone can explain me why.
This is my working code:
const fbx = useLoader(FBXLoader, "assets/models/" + props.path + ".fbx");
fbx.children.forEach((mesh, i) => {
mesh.material = matAluBright;
});
I trained my model with Google Teachable Machines (Image) and inclueded the model into my Ionic Angular app. I loaded the model successfully and used the camera preview for predicting the class which is shown in the image from the camera.
The picture which is displayed in the canvas changes properly but the predict()-method returns the same result for every call.
import * as tmImage from '#teachablemachine/image';
...
async startPrediction() {
this.model = await tmImage.load(this.modelURL, this.metadataURL);
this.maxPredictions = this.model.getTotalClasses();
console.log('classes: ' + this.maxPredictions); //works properly
requestAnimationFrame(() => {
this.loop();
});
}
async loop() {
const imageAsBase64 = await this.cameraPreview.takeSnapshot({ quality: 60 });
const canvas = document.getElementById('output') as HTMLImageElement;
//image changes properly, I checked it with a canvas output
canvas.src = 'data:image/jpeg;base64,' + imageAsBase64;
const prediction = await this.model.predict(canvas);
for (let i = 0; i < this.maxPredictions; i++) {
const classPrediction =
prediction[i].className + ': ' + prediction[i].probability.toFixed(2);
//probability doesn't change, even if I hold the camera close over a trained image
}
requestAnimationFrame(() => {
this.loop();
});
}
The prediction result is e.g.: class1 = 0.34, class2 = 0.66 but doesn't change.
I hope you could help me to find my bug, thanks in advance!
The image has probably not yet been loaded before you are calling the prediction model. It has been discussed here and there
function load(url){
return new Promise((resolve, reject) => {
canvas.src = url
canvas.onload = () => {
resolve(canvas)
}
})
}
await load(base64Data)
// then the image can be used for prediction
Currently, I have a scraper that scrapes slack messages and stores them in a db somewhere.
On the frontend, I am pulling every second to see if new messages pop up. And then I render those messages on screen.
If anyone on slack replies or emotes on a message, the message gets removed from the backend thus getting removed from the frontend.
What I am trying to do now is when an item gets removed, I would like to animate it somehow.
Here is some of my current code:
async componentDidMount() {
await this.grab_channels()
await this.grab_slack_user_data()
await this.grab_items()
setInterval(() => {
this.grab_items()
}, this.state.settings.seconds_per_slack_messages_pull * 1000 )
}
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise( resolve => {
axios.get( url )
.then( res => {
this.setState( { items: res.data } )
resolve()
} )
})
}
And finally, items get rendered:
this.props.items.map( t => {
return (
<Item
key={ t.usr + '_' + t.ts }
task={ t }
user={ this.props.slack_users[ t.usr ] }
settings={ this.props.settings }
now={ this.state.now }
/>
)
} )
I was thinking of doing some sort of check within grab_items() but I wouldn't know how to continue after that. It would be easy to determine which ones should be rendered out but the problem is actually doing it.
Anyone have experience building something like this out?
Thanks!
Using Transition Groups is one way to do this:
https://github.com/reactjs/react-transition-group
Take a look at this example:
https://reactcommunity.org/react-transition-group/transition-group
For the check part in your function grab_items
/* include "clone" so that we don't modify state directly */
import clone from 'clone'
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise(resolve => {
axios.get(url).then(res => {
/* figure out what items to remove before you set the state */
let itemsToShow = []
for (let i = 0; i < this.state.items.length; i++) {
let ifFound = false
let t = clone(this.state.items[i])
for (let j = 0; j < res.data.length; j++) {
if (t.key === res.data[j].key) {
ifFound = true
}
}
/* if ifFound is false, it means it is not in the messages any more. */
if(!ifFound){
t.haveAnimation = true
itemsToShow.push(t)
}
itemsToShow = itemsToShow.concat(res.data)
this.setState(itemsToShow)
}
})
})
}
Then every second when it re-pull the data, you will have a list of items to show. The list has the items need to have the "disappear" animation and also it has the new messages.
To make the animation work, in the render part:
this.props.items.map(t => {
return (
<Item
key={t.usr + '_' + t.ts}
className={t.haveAnimation ? 'animationCSS' : ''}
task={t}
user={this.props.slack_users[t.usr]}
settings={this.props.settings}
now={this.state.now}
/>
)
}
Above code should attach the css class to the Item. You can put whatever css animation in the class
I'm having an issue minifying one of my directives.
(function () {
angular.module("inflightApp.bnLazySrc", [])
.directive(
"bnLazySrc",
function( $window, $document) {
// I manage all the images that are currently being
// monitored on the page for lazy loading.
var lazyLoader = (function() {
// I maintain a list of images that lazy-loading
// and have yet to be rendered.
var images = [];
// I define the render timer for the lazy loading
// images to that the DOM-querying (for offsets)
// is chunked in groups.
var renderTimer = null;
var renderDelay = 100;
// I cache the window element as a jQuery reference.
var win = $( $window );
// I cache the document document height so that
// we can respond to changes in the height due to
// dynamic content.
var doc = $document;
var documentHeight = doc.height();
var documentTimer = null;
var documentDelay = 2000;
// I determine if the window dimension events
// (ie. resize, scroll) are currenlty being
// monitored for changes.
var isWatchingWindow = false;
// ---
// PUBLIC METHODS.
// ---
// I start monitoring the given image for visibility
// and then render it when necessary.
function addImage( image ) {
images.push( image );
if ( ! renderTimer ) {
startRenderTimer();
}
if ( ! isWatchingWindow ) {
startWatchingWindow();
}
}
// I remove the given image from the render queue.
function removeImage( image ) {
// Remove the given image from the render queue.
for ( var i = 0 ; i < images.length ; i++ ) {
if ( images[ i ] === image ) {
images.splice( i, 1 );
break;
}
}
// If removing the given image has cleared the
// render queue, then we can stop monitoring
// the window and the image queue.
if ( ! images.length ) {
clearRenderTimer();
stopWatchingWindow();
}
}
// ---
// PRIVATE METHODS.
// ---
// I check the document height to see if it's changed.
function checkDocumentHeight() {
// If the render time is currently active, then
// don't bother getting the document height -
// it won't actually do anything.
if ( renderTimer ) {
return;
}
var currentDocumentHeight = doc.height();
// If the height has not changed, then ignore -
// no more images could have come into view.
if ( currentDocumentHeight === documentHeight ) {
return;
}
// Cache the new document height.
documentHeight = currentDocumentHeight;
startRenderTimer();
}
// I check the lazy-load images that have yet to
// be rendered.
function checkImages() {
// Log here so we can see how often this
// gets called during page activity.
console.log( "Checking for visible images..." );
var visible = [];
var hidden = [];
// Determine the window dimensions.
var windowHeight = win.height();
var scrollTop = win.scrollTop();
// Calculate the viewport offsets.
var topFoldOffset = scrollTop;
var bottomFoldOffset = ( topFoldOffset + windowHeight );
// Query the DOM for layout and seperate the
// images into two different categories: those
// that are now in the viewport and those that
// still remain hidden.
for ( var i = 0 ; i < images.length ; i++ ) {
var image = images[ i ];
if ( image.isVisible( topFoldOffset, bottomFoldOffset ) ) {
visible.push( image );
} else {
hidden.push( image );
}
}
// Update the DOM with new image source values.
for ( var i = 0 ; i < visible.length ; i++ ) {
visible[ i ].render();
}
// Keep the still-hidden images as the new
// image queue to be monitored.
images = hidden;
// Clear the render timer so that it can be set
// again in response to window changes.
clearRenderTimer();
// If we've rendered all the images, then stop
// monitoring the window for changes.
if ( ! images.length ) {
stopWatchingWindow();
}
}
// I clear the render timer so that we can easily
// check to see if the timer is running.
function clearRenderTimer() {
clearTimeout( renderTimer );
renderTimer = null;
}
// I start the render time, allowing more images to
// be added to the images queue before the render
// action is executed.
function startRenderTimer() {
renderTimer = setTimeout( checkImages, renderDelay );
}
// I start watching the window for changes in dimension.
function startWatchingWindow() {
isWatchingWindow = true;
// Listen for window changes.
win.on( "resize.bnLazySrc", windowChanged );
win.on( "scroll.bnLazySrc", windowChanged );
// Set up a timer to watch for document-height changes.
documentTimer = setInterval( checkDocumentHeight, documentDelay );
}
// I stop watching the window for changes in dimension.
function stopWatchingWindow() {
isWatchingWindow = false;
// Stop watching for window changes.
win.off( "resize.bnLazySrc" );
win.off( "scroll.bnLazySrc" );
// Stop watching for document changes.
clearInterval( documentTimer );
}
// I start the render time if the window changes.
function windowChanged() {
if ( ! renderTimer ) {
startRenderTimer();
}
}
// Return the public API.
return({
addImage: addImage,
removeImage: removeImage
});
})();
// ------------------------------------------ //
// ------------------------------------------ //
// I represent a single lazy-load image.
function LazyImage( element ) {
// I am the interpolated LAZY SRC attribute of
// the image as reported by AngularJS.
var source = null;
// I determine if the image has already been
// rendered (ie, that it has been exposed to the
// viewport and the source had been loaded).
var isRendered = false;
// I am the cached height of the element. We are
// going to assume that the image doesn't change
// height over time.
var height = null;
// ---
// PUBLIC METHODS.
// ---
// I determine if the element is above the given
// fold of the page.
function isVisible( topFoldOffset, bottomFoldOffset ) {
// If the element is not visible because it
// is hidden, don't bother testing it.
if ( ! element.is( ":visible" ) ) {
return( false );
}
// If the height has not yet been calculated,
// the cache it for the duration of the page.
if ( height === null ) {
height = element.height();
}
// Update the dimensions of the element.
var top = element.offset().top;
var bottom = ( top + height );
// Return true if the element is:
// 1. The top offset is in view.
// 2. The bottom offset is in view.
// 3. The element is overlapping the viewport.
return(
(
( top <= bottomFoldOffset ) &&
( top >= topFoldOffset )
)
||
(
( bottom <= bottomFoldOffset ) &&
( bottom >= topFoldOffset )
)
||
(
( top <= topFoldOffset ) &&
( bottom >= bottomFoldOffset )
)
);
}
// I move the cached source into the live source.
function render() {
isRendered = true;
renderSource();
}
// I set the interpolated source value reported
// by the directive / AngularJS.
function setSource( newSource ) {
source = newSource;
if ( isRendered ) {
renderSource();
}
}
// ---
// PRIVATE METHODS.
// ---
// I load the lazy source value into the actual
// source value of the image element.
function renderSource() {
element[ 0 ].src = source;
}
// Return the public API.
return({
isVisible: isVisible,
render: render,
setSource: setSource
});
}
// ------------------------------------------ //
// ------------------------------------------ //
// I bind the UI events to the scope.
function link( $scope, element, attributes ) {
var lazyImage = new LazyImage( element );
// Start watching the image for changes in its
// visibility.
lazyLoader.addImage( lazyImage );
// Since the lazy-src will likely need some sort
// of string interpolation, we don't want to
attributes.$observe(
"bnLazySrc",
function( newSource ) {
lazyImage.setSource( newSource );
}
);
// When the scope is destroyed, we need to remove
// the image from the render queue.
$scope.$on(
"$destroy",
function() {
lazyLoader.removeImage( lazyImage );
}
);
}
// Return the directive configuration.
return({
link: link,
restrict: "A"
});
});
}());
Because you need to annotate your functions with the names of the dependencies.
See the documentation.