React-planner Threejs doesn't load texture - javascript

I have installed react-planner that uses three.js as a package into a react app and managed to get it to render etc. The planner uses a catalog that contains several items that have their own respective textures, I have saved this alongside my src folder in the main app as well according to how their demo is set up.
When loading the app in the react app it doesn't load the textures or any images and leaves the elements without the required textures, instead its a simple gray colour in the case of the wall.
However when I run the package by itself through its own webpack config it does manage to load all the required textures and files.
This is the code for the wall:
import {ElementsFactories} from 'react-planner';
const info = {
title: 'wall',
tag: ['wall'],
description: 'Wall with bricks or painted',
image: require('./wall.png'),
visibility: {
catalog: true,
layerElementsVisible: true
}
};
const textures = {
bricks: {
name: 'Bricks',
uri: require('./textures/bricks.jpg'),
lengthRepeatScale: 0.01,
heightRepeatScale: 0.01,
normal: {
uri: require('./textures/bricks-normal.jpg'),
lengthRepeatScale: 0.01,
heightRepeatScale: 0.01,
normalScaleX: 0.8,
normalScaleY: 0.8
}
},
};
export default ElementsFactories.WallFactory('wall', info, textures);
This is the part of my wall-factory-3d.js that I presume takes in the texture and is what I presume responsible for applying the texture to the wall object:
const applyTexture = (material, texture, length, height) => {
let loader = new TextureLoader();
if (texture) {
material.map = loader.load(texture.uri);
material.needsUpdate = true;
material.map.wrapS = RepeatWrapping;
material.map.wrapT = RepeatWrapping;
material.map.repeat.set(length * texture.lengthRepeatScale, height * texture.heightRepeatScale);
if (texture.normal) {
material.normalMap = loader.load(texture.normal.uri);
material.normalScale = new Vector2(texture.normal.normalScaleX, texture.normal.normalScaleY);
material.normalMap.wrapS = RepeatWrapping;
material.normalMap.wrapT = RepeatWrapping;
material.normalMap.repeat.set(length * texture.normal.lengthRepeatScale, height * texture.normal.heightRepeatScale);
}
}
};
The catalog isn't registering the JPG and the PNG files for the textures nor the elements.
This is what I see when I run the app using its own webpack
And this is what I see when I use it as a package, it is getting the planner-element.js file but not registering the textures or any images from the catalog folder for some reason?
What should I look for or investigate? The main app is a standard react app, the package is using older webpack and is a class based app that has no maintainers anymore so its not possible for me to get help from them unfortunately.
I am wondering if it is loading the URL wrong for the textures/images?
Any pointers would be appreciated, thanks!

Related

Proper way to import and use node modules with Laravel Mix

I'm just starting to use Laravel (v 8.x) Mix in a project, and am finding it frustrating to implement Javascript from node modules.
To begin, I have this in my webpack.mix.js:
mix.js('node_modules/mxgraph/javascript/mxClient.min.js', 'public/js');
mix.js('resources/js/*.js', 'public/js').postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]).version();
Next, my app.js contains the following:
import Canvas from './canvas';
require('mxgraph');
const canvas = new Canvas();
...which imports canvas.js:
export default class Canvas {
constructor() {
this.container = document.getElementById('graphContainer');
if (!mxClient.isBrowserSupported())
{
// Displays an error message if the browser is not supported.
alert('Browser is not supported!');
}
.
.
.
}
}
...and in the Scripts section of my Blade layout:
<script src="{{ mix('js/mxClient.min.js') }}" defer></script>
<script src="{{ mix('js/app.js') }}" defer></script>
When I load the page, I get the following error in the console:
Uncaught ReferenceError: mxClient is not defined
at new Canvas (app.js:3866)
at Module../resources/js/app.js (app.js:3813)
at __webpack_require__ (app.js:114081)
at app.js:114143
at app.js:114149
var mxClient is definitely present in mxClient.min.js, and the reference to it in Canvas occurs after it's loaded.
I've tried a bunch of variations, and nothing seems to work. Any guidance would be greatly appreciated.
After a lot more poking and searching around, I found a method that works. It was based on what I found here:
https://www.programmersought.com/article/85491421858/
My implementation seems a bit clunky, but it does work, and I can now continue the project I'm trying to develop with mxGraph.
Thus, I no longer pull in mxgraph explicitly in webpack.mix.js, so it now reverts to the Laravel default:
mix.js('resources/js/*.js', 'public/js').postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]).version();
I've also removed require('mxgraph'); from app.js, so it just looks like this:
import Canvas from './canvas';
const canvas = new Canvas();
My canvas.js now looks like this:
import mx from 'mxgraph';
const mxgraph = mx({
mxImageBasePath: './src/images',
mxBasePath: './src'
});
window.mxGraph = mxgraph.mxGraph;
window.mxGraphModel = mxgraph.mxGraphModel;
window.mxEvent = mxgraph.mxEvent;
window.mxEditor = mxgraph.mxEditor;
window.mxGeometry = mxgraph.mxGeometry;
window.mxRubberband = mxgraph.mxRubberband;
window.mxDefaultKeyHandler = mxgraph.mxDefaultKeyHandler;
window.mxDefaultPopupMenu = mxgraph.mxDefaultPopupMenu;
window.mxStylesheet = mxgraph.mxStylesheet;
window.mxDefaultToolbar = mxgraph.mxDefaultToolbar;
const {mxGraph, mxClient, mxEvent, mxCodec, mxUtils, mxConstants, mxPerimeter, mxRubberband} = mxgraph;
export default class Canvas {
constructor() {
let container = document.getElementById('graphContainer');
if (typeof(mxClient) !== 'undefined') {
this.draw(container);
}
}
draw (container) {
if (! mxClient.isBrowserSupported())
{
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
}
else
{
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
var graph = new mxGraph(container);
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try
{
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v1, v2);
}
finally
{
// Updates the display
graph.getModel().endUpdate();
}
}
}
}
Most of the code in the draw() method is taken from an mxGraph "Hello World" demo (https://jgraph.github.io/mxgraph/docs/manual.html).
Many thanks to #codedge for the time you spent helping me!
So, after playing around I found what you can do.
Assemble one file
Put mxClient.min.js and app.js into one file by doing
mix.js(
[
"resources/js/app.js",
"node_modules/mxgraph/javascript/mxClient.min.js",
],
"public/js"
).postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]).version();
Import mxGraph and Canvas
In your app.js you can put that
import "./canvas";
require("mxgraph");
const canvas = new Canvas();
and npm run dev runs without issues.
Update
I found a (maybe easier) option. Don't include mxClient in your webpack.mix.js, only your app.js is needed.
// app.js
window.mxClient = new require("mxgraph")().mxClient;
let isBrowserSupported = mxClient.isBrowserSupported();
console.log(isBrowserSupported);

Stop pixel font from being blurred when rendered

I am trying to create a little game in a pixel retro look and I am using a pixelated font. Unfortunately I am unable to render the font with edges as sharp as they are in the source font.
About my mcve:
I am using this font for fonts/pokemon_classic.ttf (I found no way to host that font online, so no jsfiddle), but you can use any pixel font you like.
The example below renders the font like this:
How can I make this text render as sharp as it is in the source font? It should look like this (edited image):
the scale of root may change during runtime to fit the screen
A less elegant solution which would probably work is to fix the alpha of each pixel to be either 0 or 1 depending on some threshold, but I don't know how to do this.
JS:
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
let scale = 30;
let app = new PIXI.Application();
document.body.appendChild(app.view);
app.renderer.view.style.position = "absolute";
app.renderer.view.style.display = "block";
app.renderer.autoResize = true;
app.renderer.resize(window.innerWidth, window.innerHeight);
let root = new PIXI.Container();
app.stage.addChild(root);
root.scale.set(scale);
document.fonts.load('8pt "pokemon"').then(() => {
let text = new PIXI.Text("Test", {fontFamily: 'pokemon', fontSize: 8, fill: 0xff1010});
root.addChild(text);
});
CSS:
#font-face {
font-family: 'pokemon';
src: url("fonts/pokemon_classic.ttf");
}
* {
padding: 0;
margin: 0;
}
Things that can make text blurry....
SCALE_MODE
Sub-pixel positioning. Turn on roundPixels when creating new PIXI.Application for v4, v5 you can set globally via PIXI.settings.ROUND_PIXELS = true;, or indivudally, displayObject.roundPixels = true;
Scaling
So you're good on #1, but #2 and #3 could be issues.
I resolved it by setting resolution like this:
text.resolution = window.devicePixel * scale
It will re-render the text leading to bad performance, so I just apply it for the last value of scale.
If you don't mind having a double resolution for the whole app...
My Pixi.js text and textures were blurry.
I increased the resolution of the whole Pixi app.
Then your canvas size will double.
You can then you options.autoDensity to fit double resolution in the same canvas. https://pixijs.download/dev/docs/PIXI.Application.html
// Pixi.js API from v6.5.1
import * as PIXI from 'pixi.js';
PIXI.settings.PRECISION_FRAGMENT = PIXI.PRECISION.HIGH; // might help a bit
PIXI.settings.ROUND_PIXELS = true; // might help a bit
PIXI.settings.RESOLUTION = 2;
const app = new PIXI.Application({ width: 750, height: 400, autoDensity: true });

loading cached fonts at the right time

I have some webfonts that get loaded via Webfontloader load like this ...
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js"></script>
<script>
WebFont.load({
custom: {
families: ['BlenderProBook', 'BlenderProMedium']
}
});
</script>
And it works great when first loading the page ... Problems is, when refreshing the page it only retrieves the cached fonts when requested in html and not before my ReactJS app runs (when the Webfontloader normally gets them). This is too late for me, because I'm using them in pre-generated SVG.
Is there a way to force it to get the uncached fonts each time? Or better, load the cached fonts at the correct time.
I found a solution to this, which I will take as my answer if no-one else can supply a better one.
My font is being used extensively in the app to calculate text widths and therefore layout ... (which is why it needs to be loaded before the app runs), so I decided to use the text width code as a test to block the app from running.
Here's the code
var testFont = require('./components/svgText.js');
var timer = setInterval(function () {
var test = (testFont('TEST', 'BlenderProBook', 20).width === 39.75);
if (test) {
clearInterval(timer);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
}
}, 50);
where svgTest.js uses Raphael SVG to generate SVG text and retrieve the measurements like this:-
renderedTextSize = function (string, font, fontSize) {
var paper = Raphael(0, 0, 0, 0);
paper.canvas.style.visibility = 'hidden';
var el = paper.text(0, 0, string);
el.attr('font-family', font);
el.attr('font-size', fontSize);
var bBox = el.getBBox();
paper.remove();
return {
width: bBox.width,
height: bBox.height
};
};
module.exports = renderedTextSize;
This seems to work quite nicely, even though it feels a bit hacky to be testing against a magic number (width).

Performance issues using images with arbor.js

I've been working on adapting arbor.js to use images.
However, being a relative JS noob what I have is totally un-optimised.
As far as I can tell, the way I've set it up is recreating the image object for every image and every frame, resulting in tons of flicker.
Can anyone suggest a way to move the new Image() stuff out of the redraw function into the initiation? As far as I know this is a basic OOP issue, but totally stuck.
Thanks!
Pastebin of where I'm up to on the output script
Current status.
Apologies all! There's a few steps. I'll highlight the key stages, the rest is from the tutorial.
First, add the relevant information to your JSON, for example:
nodes:{
innovation:{ 'color':colour.darkblue,
'shape':'dot',
'radius':30,
'image': 'innovation.png',
'image_w':130,
'image_h':24,
'alpha':1 },
participation:{ 'color':colour.purple,
'shape':'dot',
'radius':40,
'image':'participation.png',
'image_w':130,
'image_h':24,
'alpha':1 },
...
Cache all your images when the thing loads.
init:function(system){
// Normal initialisation
particleSystem = system
particleSystem.screenSize(canvas.width, canvas.height)
particleSystem.screenPadding(25, 50)
that.initMouseHandling()
// Preload all images into the node object
particleSystem.eachNode(function(node, pt) {
if(node.data.image) {
node.data.imageob = new Image()
node.data.imageob.src = imagepath + node.data.image
}
})
...
Then, for moving the images themselves...
particleSystem.eachNode(function(node, pt){
...
// Image info from JSON
var imageob = node.data.imageob
var imageH = node.data.image_h
var imageW = node.data.image_w
...
// Draw the object
if (node.data.shape=='dot'){
// Check if it's a dot
gfx.oval(pt.x-w/2, pt.y-w/2, w,w, {fill:ctx.fillStyle, alpha:node.data.alpha})
nodeBoxes[node.name] = [pt.x-w/2, pt.y-w/2, w,w]
// Does it have an image?
if (imageob){
// Images are drawn from cache
ctx.drawImage(imageob, pt.x-(imageW/2), pt.y+radius/2, imageW, imageH)
}
}else {
// If none of the above, draw a rectangle
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle, alpha:node.data.alpha})
nodeBoxes[node.name] = [pt.x-w/2, pt.y-11, w, 22]
}
...

Hide overflow on phaser

What I'm trying to do sounds pretty simple, but I can't do it.
I am building a game using Phaser, and on the top bar of my game want to print the picture of the player inside a circle. But I can't find a way to do this.
I find how to do a circle :
this.mask = this.game.add.graphics(0,0);
this.mask.drawCircle(50,50,50);
But I can't find a way to fill it with a picture, without the picture overflowing the circle.
Phaser has support for using an image to mask another.
See the official Alpha Mask example for an example with two images. Using an image for the mask might be the recommended method.
I've also created a JSFiddle that shows how to use a created circle:
// Create our 'main' state that will contain the game
var mainState = {
preload: function() {
// This function will be executed at the beginning
// That's where we load the images and sounds
this.load.crossOrigin = 'anonymous';
this.load.image('baseImage', 'https://placeholder.baker.com/200');
},
create: function() {
this.baseImage = this.game.add.sprite(this.world.centerX, this.world.centerY * .5, 'baseImage');
this.baseImage.anchor.setTo(0.5);
this.mask = game.add.bitmapData(this.baseImage.width, this.baseImage.height);
this.mask.circle(this.world.centerX, this.world.centerY * .5, 100, 'rgb(0,200,0)');
this.bmd = this.game.make.bitmapData(200, 200);
this.bmd.alphaMask('baseImage', this.mask);
this.game.add.image(this.game.world.centerX, this.world.centerY * 1.5, this.bmd).anchor.set(0.5);
},
};
// Initialize Phaser, and create a game
var game = new Phaser.Game(200, 400);
// Add the 'mainState' and call it 'main'
game.state.add('main', mainState);
// Start the state to actually start the game
game.state.start('main');

Categories