I'm making some enemies and I want all of them to update their position before render.
I could make an update() function, but i've tryed to make onBeforeRender() fucntion attached to all of them.
The problem is that nothing happens.
Here is my code, i don't know where it fails.
var i = 0;
for(i =0; i < num_enemics; i++ ){
//CLONE THE ENEMY FROM THE PROTOTYPE
var enemic = dolent.clone(true);
enemic.name = i.toString();
enemic.visible = true;
//SPAWNS ON RENDER POSITION
enemic.position.x = Math.random() *(40) - 20;
enemic.position.y = 0.7;
enemic.position.z = Math.random() * (28) - 14;
self.veloicity = new THREE.Vector3(0.1,0,0);
//FUNCTION TO BE CALLED BEFORE RENEDER
enemic.onBeforeRender(function(){
self.position.addVectors(self.position,self.velocity);
});
scene.add(enemic);
}
Here is the pattern to follow when defining onBeforeRender:
object.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
// your code here
this.position.add( this.velocity );
};
Related
I've got the below code, which takes in a number of scans. I know this to always be 2880. The canvas should split an entire 360° into 2880 sectors. The loop in the code below will always run from 0 to 2880, and in each loop, a bunch of (maybe several hundred) 2px coloured points are rendered in that sector, emanating from the centre of the canvas outward. The loop moves fast, before I upgraded the THREE package, this loop could render in c. 15 seconds.
The picture draws correctly, but what confuses me is the fact that the call to THREE's render message happens inside of the loop, yet the picture draws nothing until the last iteration of the loop is complete and then all 2880 sectors appear at once, which isn't the effect I'm going for.
Can anyone advise what I might be missing? It's a 2-D non-interactable image.
Stuff I've tried:-
setTimeout(null, 1000) after the .render() method to make it wait before executing the next iteration of the loop
Considered making it a recursive function with the next iteration of the loop inside of the above setTimeout
Reversing the THREE upgrade as an absolute last resort.
Stuff I've considered:-
Is the loop running two fast for the frame rate or not giving the screen enough time to update?
Limitation of THREEJs?
const drawReflectivityMap = (scans, reflectivityData, azimuthData, scene, renderer, totalScans, currentScan, cameraPosition, camera, reflectivityColours) => {
currentCamera = camera;
currentRenderer = renderer;
for (let i = 0; i < scans; i++) {
console.log('Drawing Reflectivity ' + i);
var reflectivity = reflectivityData[i];
var azimuth = utils.radians(azimuthData[i]);
var sinMultiplier = Math.sin(azimuth);
var cosMultiplier = Math.cos(azimuth);
var initialRange = mikeUtilities.addRange(mikeUtilities.multiplyRange(mikeUtilities.createRange(0, reflectivity.GateCount, 1), reflectivity.GateSize), reflectivity.FirstGate);
var x = utils.multiplyRange(initialRange, sinMultiplier);
var y = utils.multiplyRange(initialRange, cosMultiplier);
var dataSet = {
x: x,
y: y,
reflectivity: reflectivity
};
var reflectivityColourScale = d3.scaleQuantize().domain([-32.0, 94.5]).range(reflectivityColours);
var pointsMaterial = new THREE.PointsMaterial({
size: 2,
vertexColors: true,
sizeAttenuation: false
});
// x co-ordinate points for each point in this arc
var x = dataSet.x;
// y co-ordinate points for each point in this arc
var y = dataSet.y;
// Reflectivity (rainfall) intensity values for each point in this arc
var reflectivity = dataSet.reflectivity;
var geometry = new THREE.BufferGeometry();
var pointsGraph = [];
var coloursGraph = [];
x.forEach(function (index, i) {
if (reflectivity.MomentDataValues[i] > -33) {
geometry = new THREE.BufferGeometry();
var dataPointColour = new THREE.Color(reflectivityColourScale(reflectivity.MomentDataValues[i]));
pointsGraph.push(x[i], y[i], 0);
coloursGraph.push(dataPointColour.r, dataPointColour.g, dataPointColour.b);
}
});
var pointsGraphArray = new Float32Array(pointsGraph);
var coloursGraphArray = new Float32Array(coloursGraph);
geometry.setAttribute('position', new THREE.BufferAttribute(pointsGraphArray, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(coloursGraphArray, 3));
var pointsMap = new THREE.Points(geometry, pointsMaterial);
scene.add(pointsMap);
renderScene(scene, cameraPosition, renderer);
}
}
function renderScene(scene, cameraPosition,renderer) {
currentCamera.position.z = cameraPosition;
currentRenderer.render(scene, currentCamera);
requestAnimationFrame(renderScene);
}
for (let i = 0; i < scans; i++) {
setTimeout(() => {
// Your code goes here
} i * 100)
}
I am building an app in three.js, and basically the point is to have rows and columns of individual blocks that also extend upwards. When you click on a block it will change colour, so I have implemented clipping along all three axes, so that the middle blocks can be reached as well. I have also added a Gui using lil-gui, and that is where the problem comes in. I have controls for the amount of rows, columns and the height, as well as for how much it should clip in each direction. When initialized originally, the rows, columns and height are all set to 1, and if you increase any of them from there, the amount of blocks will correctly update in the specified direction.
My problem comes in when I load a previous save. The previous state is saved to localStorage, in a function that runs every few minutes, and saving also occurs with ctrl + s. It converts my current state, a 3 dimensional array, into a much simpler object, which is also a 3 dimensional object. It saves correctly, I have logged the output multiple times. When it loads, I convert the string into a JSON object, which I then convert into a state object, and I then render that state object. The strangest thing is that it only displays roughly half of what it should in all 3 directions, E.G A 3x3x3 grid is rendered as a 2x2x2 grid, but as soon as I click on rows, columns or height in my GUI, and click out again, it magically renders properly. What is also odd is that although all of my GUI fields have a min, a max and a step, they are unconstrained in the gui.
I have simplified my code so that it only deals with rows, as an mcve but the problem scales up as well.
The lil-gui panel code
function setUpPanel(){
const panel = new GUI( {width:310} );
const folder1 = panel.addFolder( "Size" );
const folder2 = panel.addFolder( "X Clipping" );
settings = {
'rows' : rows,
planeX: {
blocks: rows,
},
};
rowsObj = folder1.add(settings, 'rows').min(1).step(1).max(50).setValue( rows ).onFinishChange( function ( size ) {
rowsElement.max(size);
rowsElement.setValue(size);
rowsElement.updateDisplay();
rows = size;
redraw();
} );
rowsElement = folder2.add(settings.planeX, 'blocks',1, rows, 1).onChange( setClippingPlaneRows );
}
The code called when the app is started
import * as THREE from './js/three.module.js';
import { GUI } from './js/lil-gui.module.min.js';
import { OrbitControls } from './js/OrbitControls.js';
const blockOffset = 0.1;
const blockSize = 1;
const BlockStates = {
Empty: Symbol('e'),
}
const clippingPlanes = [
new THREE.Plane( new THREE.Vector3( -1, 0, 0 ), 0, {name: "plane"} ),
];
//gui object
let rowsObj
//more gui object
let rowsElement
const emptyMaterial = new THREE.MeshStandardMaterial( {color: 0xffffff, opacity: 0.5, transparent: true} );
emptyMaterial.clippingPlanes = clippingPlanes;
let rows=1;
let state = [];
let boxes = new THREE.Group();
let settings;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight , 0.1, 1000 );
camera.position.set(-1.5,2.5,3);
const renderer = new THREE.WebGLRenderer();
renderer.localClippingEnabled = true;
renderer.setSize( window.innerWidth, window.innerHeight);
document.body.appendChild( renderer.domElement );
var autoSaveID = setInterval(function() {
localStorage.lastSave = JSON.stringify(createJsonObjectFromState());
localStorage.rows = rows;
}, 300000);
if(localStorage.lastSave){
let loadSave = confirm("There is a previous save detected. Would you like to load it?");
if(loadSave){
const loadedObj = createStateObjectFromJsonString(localStorage.lastSave);
state = loadedObj.state;
boxes = loadedObj.boxes;
rows = localStorage.rows;
boxes.position.set(-(rows * (blockSize + blockOffset)- blockSize - blockOffset)/2,0, 0);
}
}
const light = new THREE.AmbientLight( 0x404040, 10);
const controls = new OrbitControls(camera, renderer.domElement );
setUpPanel();
drawBoxes();
boxes.position.set(0,0,0);
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 's') {
// Prevent the Save dialog from opening
e.preventDefault();
saveCurrent();
}
});
scene.add( boxes );
scene.add( light );
renderer.render(scene, camera);
The save function
function saveCurrent(){
let stringVersion = JSON.stringify(createJsonObjectFromState());
//save to local storage
if(localStorage.lastSave){
let writeSave = confirm("There is a previous save detected. Would you like to overwrite it?");
if(writeSave){
localStorage.lastSave = stringVersion;
localStorage.rows = rows;
}
}
else{
localStorage.lastSave = stringVersion;
localStorage.rows = rows;
}
}
createStateObjectFromJsonString
function createStateObjectFromJsonString(jsonString){
let newObj =[];
let newBoxes=new THREE.Group();
let jsonObj = JSON.parse(jsonString);
for(let i = 0; i < jsonObj.length; i++){
let box;
switch(jsonObj[i]){
case "E":
box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
newBoxes.add(box);
newObj.push({state: BlockStates.Empty, item:box});
break;
}
}
return {state: newObj, boxes: newBoxes};
}
createJsonObjectFromState
function createJsonObjectFromState(){
const JSONObj = [];
for(let i = 0; i < rows; i++){
switch(state[i].state){
case BlockStates.Empty:
JSONObj.push("E");
break;
}
}
return JSONObj;
}
The function to set the clipping to a specific amount of blocks
function setClippingPlaneRows(amt){
clippingPlanes[0].constant = rows%2 === 0 ? (blockSize + blockOffset) * (amt - Math.floor(rows/2) ) : (blockSize + blockOffset) *( amt - Math.floor(rows/2) - 0.5);
}
The redraw function
function redraw(){
drawBoxes();
}
The drawBox function
function drawBox(state){
const geometry = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
let material = emptyMaterial;
return new THREE.Mesh( geometry, material );
}
The drawBoxes function
function drawBoxes(){
if(state.length === 0)
{
boxes.clear();
state = [];
for(let i = 0; i < rows; i++){
const box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
boxes.add(box);
state.push({state : BlockStates.Empty, item: box});
}
}
else{
if(state.length <= rows){
//just draw in the extra rows
for(let i = state.length; i < rows; i++){
const box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
boxes.add(box);
state.push({state : BlockStates.Empty, item: box});
}
}
else{
//give a warning, and if accepted remove neccesary rows
let accepted = confirm("Making the rows smaller could remove some of the work you have done. Are you sure?");
if(accepted){
for(let i = 0; i < state.length - rows; i++){
const box = state[state.length - 1].item;
boxes.remove(box);
state.pop();
}
}
}
}
//plane initial positions
boxes.position.set(-(rows * (blockSize + blockOffset)- blockSize - blockOffset)/2, 0, 0);
setClippingPlaneRows(rows);
}
The animation part
function animate() {
let frame = requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
I apologize for posting so much code, I just have no idea where the problem is stemming from. I have tried many things, such as checking the size of the rows, columns, etc at many points, but they are always correct. I have tried using setValue() to force the gui to call the onChange function, which hasn't done anything. I have tried many other variations too, but no luck.
Edit
I have discovered while playing around that if I set rows to a number other than 1 in the code itself, such as 7, and don't load a saved file, a very similar problem occurs. Only about half of the blocks are displaying, but this time the gui behaves itself and remains constrained.
I have made a collision detection. You can place objects at the raycaster/mouse position on a floor. Therefore you need to click on a button 'Add object', then you get a object(helper) that follows the mouse to see if the new object get a collision with another object. When you click on the position you want, the new object will be placed to the world if there is no collision.
The collision detection I have made works perfectly when the object that is already placed in the world has the same size as the helper/new object.
On the next screenshot you can see a big object and a small(red) helper. The color red means that there is a collision. When I move the mouse more to the right it turns green.
Why does my collision detection work only with 2 objects that have the same size and why doesn't it not with different ones?
Here is my code in the Click event to show the big object:
var geometry = new THREE.BoxGeometry(200, 200, 300);
var bigobject = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xFBF5D7,
opacity: 1
}));
bigobject.position.copy(intersects[0].point);
bigobject.position.y = 100;
objects.push(bigobject);
scene.add(bigobject);
Here is my code to show the helper when the button 'Add object' is clicked:
var geometry = new THREE.BoxGeometry( 50, 50, 100 );
helper = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x00ff00, opacity: 1 } ) );
helper.name = 'helper';
scene.add( helper );
Here is my code in the MouseMove event to detect the collision:
if(scene.getObjectByName( 'helper' )) {
helper.position.copy( intersects[ 0 ].point );
helper.position.y = 25;
var helperWidth = helper.geometry.parameters.width;
var helperLength = helper.geometry.parameters.depth;
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
var objectWidth = objects[i].geometry.parameters.width;
var objectLength = objects[i].geometry.parameters.depth;
// MIN X
var helperMinX = helper.position.x;
var objectMinX = objects[i].position.x;
// MAX X
var helperMaxX = helperWidth + helper.position.x;
var objectMaxX = objectWidth + objects[i].position.x;
// MIN Z
var helperMinZ = helper.position.z;
var objectMinZ = objects[i].position.z;
// MAX Z
var helperMaxZ = helperLength + helper.position.z;
var objectMaxZ = objectLength + objects[i].position.z;
if (objectMinX <= helperMaxX && objectMaxX >= helperMinX && objectMinZ <= helperMaxZ && objectMaxZ >= helperMinZ) {
validpositionObject = false;
}
}
if ( validpositionObject === true ) {
helper.material.color.setHex( 0x00ff00 );
validposition = true;
}else{
helper.material.color.setHex( 0xff0000 );
validposition = false;
}
}
What goes wrong with the position when it is a big and a small object.. Can anyone help me in the right direction? Many thanks
Use this:
function collisonXZ(o1, o2) {
if (Math.abs(o1.position.x - o2.position.x) > (o1.geometry.parameters.width + o2.geometry.parameters.width) / 2)
return false;
if (Math.abs(o1.position.z - o2.position.z) > (o1.geometry.parameters.depth + o2.geometry.parameters.depth) / 2)
return false;
return true;
}
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
if (collisionXZ(helper, objects[i])) {
validpositionObject = false;
break;
}
}
Basically I'm trying to get a 3d cube to face the direction the mouse is in. It's almost there, but right now it's not rendering the cube, which it was doing fine before I added this code:
cube.look(xTarget, yTarget);
which is giving this error:
Uncaught TypeError: Cannot read property 'look' of undefined`
It's making the cube object inaccessible, why is that? (...at least, that's what I think the problem is). What am I doing wrong here?
Here's a plunker
Here's the relevant js:
Cube.prototype.updateBody = function(speed){
this.box.rotation.y += (this.tBoxRotY - this.box.rotation.y) / speed;
this.box.rotation.x += (this.tBoxRotX - this.box.rotation.x) / speed;
this.box.position.x += (this.tBoxPosX-this.box.position.x) / speed;
this.box.position.y += (this.tBoxPosY-this.box.position.y) / speed;
this.box.position.z += (this.tBoxPosZ-this.box.position.z) / speed;
}
Cube.prototype.look = function(xTarget, yTarget){
this.tBoxRotY = rule3(xTarget, -200, 200, -Math.PI/4, Math.PI/4);
this.tBoxRotX = rule3(yTarget, -200,200, -Math.PI/4, Math.PI/4);
this.tBoxPosX = rule3(xTarget, -200, 200, 70,-70);
this.tBoxPosY = rule3(yTarget, -140, 260, 20, 100);
this.tBoxPosZ = 0;
}
function loop() {
render();
var xTarget = (mousePos.x-windowHalfX);
var yTarget= (mousePos.y-windowHalfY);
console.log('Mouse X position: ' + xTarget +', Y Target = '+yTarget );
cube.look(xTarget, yTarget);
requestAnimationFrame(loop);
}
Working plunker here. http://plnkr.co/edit/3gZVI8UXRdTW7fLddj9N?p=preview
There were several problems
I changed
init();
animate();
loop();
createCube();
to
init();
createCube();
animate();
loop();
in order to fix your null reference problems. (Animate and loop require the cube to be created before they can work with it).
Also your inheritance (I assume you were going for inheritance?) was incorrect.
I updated it to
Cube = function(){
var geometry = new THREE.BoxGeometry( 50, 50, 50 );
for ( var i = 0; i < geometry.faces.length; i += 2 ) {
var hex = Math.random() * 0xffffff;
geometry.faces[ i ].color.setHex( hex );
geometry.faces[ i + 1 ].color.setHex( hex );
}
var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, overdraw: 0.5 } );
//I removed this line
//Can't do inheritance like this as far as I know?
//return box = new THREE.Mesh( geometry, material );
//And added this line instead.
//Apply your arguments to the Mesh's constructor
THREE.Mesh.apply(this, [geometry, material]);
}
//I added these lines as well...
//Set up the prototypes and constructors for inheritance
Cube.prototype = THREE.Mesh.prototype;
Cube.prototype.constructor = Cube;
Also updated Cube.prototype.updateBody to appropriately call the inherited Mesh's rotation (this.rotation.x as opposed to this.box.rotation.x)
I'm fairly new to object orientated stuff so this may very well be the wrong way to be going about getting this done.
This is a very slimmed down version of what I currently have, but the concept is essentially the same. When the user clicks on a canvas element on my page, I create 20 instances of the particle object below, append them to an array whilst at the same time updating the canvas at 30FPS and drawing circles based on the x property of the instances of each object. Once a particle is off the screen, it's removed from the array.
var particle = function()
{
var _this = this;
this.velocity = 1;
this.x = 0;
this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
var updateObject = function()
{
_this.x += velocity;
}
}
I would like the user to be able to control the velocity of new particles that are created using an input element on the page. When this is updated I have an event listener call
particle.updateVelocity(whateverTheUserEntered);
However I get the error "particle has no method updateVelocity". After a bit of reading up on the subject I understand that to call that function I need to create an instance of the object, but this will only update the velocity value of that instance which isn't going to work for me.
My question is, is there a way to achieve what I'm doing or have I approached this in completely the wrong way? As I said, I'm still getting to grips with OOP principles so I may have just answered my own question...
Try this:
var particle = new (function()
{
var _this = this;
this.velocity = 1;
this.x = 0;
this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
var updateObject = function()
{
_this.x += velocity;
}
})();
Your's is creating a function and then setting the variable particle to that value. particle will not have any special properties because of this. My example above, however, by using new and the function as a constructor assigns particle an instance of a (now anonymous) class.
I think what you want is:
// define a particle "class"
function Particle() {
var _this = {};
_this.velocity = 1;
_this.x = 0;
_this.multiplier = 1;
_this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
_this.updateObject = function()
{
_this.x += velocity;
}
return _this;
}
// make 1 particle
var myParticle = new Particle();
myParticle.updateVelocity(100);
// make a bunch of particles
var myParticles = [];
for (var i=0; i < 100; i++) {
var p = new Particle();
p.updateVelocity(Math.random * 100);
myParticles.push(p);
}
If you change it to
var particle = new function () {
}
The 'new' will cause creation of an instance.
So create a function that builds new particle instances for you.
Make velocity static and have a static method to update it. This way, you can still make instances of particle and update the velocity for all of them.
var particle = function() {
// particle stuff
}
particle.velocity = 1;
particle.updateVelocity = function(newVelocity) {
this.velocity = newVelocity
}