I have multiple pixi graphics rectangles that I want to rotate around a single point. I solved this by creating a pixi container and adding all objects to is. When rotating the container, all objects rotate accordingly. After the rotation is done, I want to remove the object from the container and put them back on the canvas.
I've done this with a normal pixi stage and container and it works just fine. However, my project requires I make use of the pixi viewport librarie for working with mouse zoom etc. when I try to remove the objects from the container and put them back on the viewport, the coordinates doesn't seem to match the correct location. I've tried many translations like toGlobal, toLocal, toWorld and toScreen for both the pixi graphics objects, containers and the viewport. None seem to give the correct results.
function startRotation(angle) {
// add all objects (in my case panels) to the container
for (let selectedPanel of selectedPanels) {
// create a container
if (container === undefined) {
container = new PIXI.Container();
viewport.addChild(container);
}
viewport.removeChild(selectedPanel.g);
container.addChild(selectedPanel.g);
}
// rotate the container
let por = pointOfRotation; // calculated somewhere else
container.position.x = por.x;
container.position.y = por.y;
container.pivot.x = por.x;
container.pivot.y = por.y;
container.rotation = Math.Radians(angle - startingRotation);
}
function finishRotation() {
if (container !== undefined) {
for (let selectedPanel of selectedPanels) {
// attempt to find the global position of the panels
// translate the panel position from container to global
let viewportX = selectedPanel.g.toGlobal(container).x;
let viewportY = selectedPanel.g.toGlobal(container).y;
let globalX = viewport.toWorld(viewportX, viewportY).x;
let globalY = viewport.toWorld(viewportX, viewportY).y;
container.removeChild(selectedPanel.g);
viewport.addChild(selectedPanel.g);
// set the new position of the panels
selectedPanel.g.x = globalX;
selectedPanel.g.y = globalY;
// set the new rotation of the panels
let panelRotation = selectedPanel.getRotation() + Math.Degrees(container.rotation);
selectedPanel.setRotation(panelRotation);
}
viewport.removeChild(container);
container = undefined;
}
}
After I remove the panels from the container and add them to the viewport, I want to set the correct position of the panels on the viewport, but the code used for this does not work.
Related
I'm trying to make a simple game. Tell me how to make object (comet) fly and rotate around orbit of another object (planet). I could only make the comet turn and fly toward the planet, but it is necessary that when a certain distance is reached, the comet starts to rotate around the planet’s orbit.
IMAGE
this.mesh.translateZ(this.speed);
if (this.target.position.distanceTo(this.mesh.position) >= 100) {
let targetQuaternion = new THREE.Quaternion();
let rotationMatrix = new THREE.Matrix4();
rotationMatrix.lookAt(this.mesh.position, this.target.position, this.mesh.up);
targetQuaternion.setFromRotationMatrix(rotationMatrix);
if (!this.mesh.quaternion.equals(targetQuaternion)) {
let step = 0.01;
this.mesh.quaternion.rotateTowards(targetQuaternion, step);
}
} else {
//how to make a comet(this.mesh) fly around the orbit of the planet(this.target)?
}
The simplest way to make one object orbit another is to utilize parent-child relationships. If you add a child to an object it shares a transformation, so when the parent is rotated the child is rotated with it - and when the child has a large translation offset, it effectively "orbits" the parent.
Very simple example of this:
init() {
this.planet = new Mesh(new SphereBufferGeometry(100))
this.satellite = new Mesh(new SphereBufferGeometry(5))
this.satelliteCenter = new Object3D()
this.satellite.position.set(1000,0,0)
this.satelliteCenter.add(this.satellite)
this.scene.add(this.planet)
this.scene.add(this.satelliteCenter)
}
update() {
this.satelliteCenter.rotateY(Math.PI / 2000) // one full revolution every 1000 frames
}
So, I'm making a game on HTML5 canvas. It's a top down shooter, and I need to create a bullet every time you click to make the character shoot.
Initially, I just prevented the player from firing another bullet until it went out of bounds or it hit an enemy, as seen here. This worked percetly, but of course, makes for uninteresting gameplay.
Then, I began researching about JS classes, and I thought that it would be the key to the problem. I created a bullet class, and moved all the logic for the bullet to the class. Then, I created an instance of it, and called it in other parts of the code to execute its logic. This worked exactly as it did before, which is good, because it meant I could translate the thing I had before to a class, but it had a similar issue.
This is how the class is defined:
class bullet{
constructor(_img, _piercing){
this.bulletPic = document.createElement("img");
this.img = this.bulletPic.src = _img;
this.piercing = _piercing;
}
shoot(){
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
bulletShot = true;
shots = 0;
}
draw(){
canvasContext.save();
canvasContext.translate(this.bulletX, this.bulletY);
canvasContext.rotate(this.bulletAngle);
canvasContext.drawImage(this.bulletPic, -this.bulletPic.width / 2, -this.bulletPic.height / 2);
canvasContext.restore();
if(bulletShot){
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
}
And here is the object definition:
let bullet1 = new bullet("Textures/player.png", true);
If I want to shoot another bullet at the same time, I need to have already defined a new instance of the bullet class, is there any way for me to define a new instance every time I click?
Edit: The shoot and draw methods are called in another file that follow logic that's not shown here. Mainly what this other code does, is detect when it hits an enemy or when it goes out of bounds to set "bulletShot" to false, that makes it "despawn", and I can shoot another bullet. This is part of the 1 bullet at a time limitation I'm trying to remove here, but that can go once this central issue is fixed.
If I understand your situation, you could use a function that returns a new class:
function bulletFactory( className ) {
return new className();
}
If you want to achieve that there could be several bullets in "mid-air", after a series of fast consecutive clicks, then create an array of bullets. You would initialise that array like this:
const bullets = Array({length: ammo}, () => new Bullet());
ammo would be the number of bullets that the user can shoot in total.
NB: I simplified the call of the constructor. Add the arguments you want to pass. Secondly, it is common practice to start class names with a capital.
Then add a state property in the Bullet instances that indicates whether the bullet is:
Hidden: it is not visible yet, but part of the total ammunition that can still be used in the future
Ready: it is the one bullet that is visible at the start location, ready to be fired by the user
Shot: a bullet that has been shot and is currently flying through the game area
At first this state is "hidden":
constructor(_img, _piercing){
this.state = "hidden";
// ...
}
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
}
Then at the start of the game, make one bullet visible (where it should be clicked):
bullets[0].state = "ready"; // From now on it will be drawn when `draw()` is called
In the click handler do the following:
// Fire the bullet the user clicked on:
bullets.find(bullet => bullet.state === "ready").shoot(playerAngle, playerX, playerY);
// See if there is a next bullet remaining in the user's ammo:
const nextBullet = bullets.find(bullet => bullet.state === "hidden");
if (nextBullet) nextBullet.state = "ready"; // Otherwise ammo is depleted.
The shoot method should not rely on global variables, but get the necessary external info as arguments:
shoot(playerAngle, playerX, playerY) {
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
this.state = "shot";
}
Don't use global variables inside your class methods (shot, ammo,...). Instead use arguments or other instance properties.
The draw method should also work with that state:
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
if(this.state === "shot") {
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
In your animation loop, you should call draw on all bullets. Something like:
bullets.forEach(bullet => bullet.draw());
I did not see any code for when a bullet has left the game area, either by hitting something or just flying out of range. In such case the bullet should be removed from the bullets array to avoid that the draw method keeps drawing things without (visual) significance.
Here is how you could delete a specific bullet:
function deleteBullet(bullet) {
const i = bullets.indexOf(bullet);
if (i > -1) bullets.splice(i, 1);
}
I hope this gets you going on your project.
I ended up making an array that contains multiple instances of the class. I defined a variable that I used as a limit and then set up a for statement to create all the objects, then, I can call them using the array name and the position.
for(var i = 0; i < arraySize; i++){
arrayName[i] = new className(parameters);
}
Examples of usage:
arrayName[5].method();
I'd like to generate custom mip maps for a rendered frame for an effect in THREE.js, but it's not clear how to achieve that. I'll want to regenerate the mip maps again after every frame.
This example shows how to generate mipmaps for a texture, but it looks like the mipmaps array requires a handle to an image or canvas tag, which a renderTarget texture does not have. Ideally I'd like to be able to do something like this:
var rt = new THREE.WebGLRenderTarget()
// initialize and render to the target
var mipmaps = [];
var width = rt.width / 2;
var height = rt.height / 2;
while( width > 2 && height > 2) {
var mip = new THREE.WebGLRenderTarget({ width, height });
mipmaps.push(mip.texture); // mip.texture.image === undefined
// render to the new mipmap with a custom downsample shader
width /= 2;
height /= 2;
}
rt.texture.mipmaps = mipmaps;
// render using the original render target as a texture map and using
// the new mip maps as needed
Is this possible? How would I achieve this?
Thank you!
I am writing a trace-line function for a visualization project that requires jumping between time step values. My issue is that during rendering, the line created using THREE.js's BufferGeometry and the setDrawRange method, will only be visible if the origin of the line is in the camera's view. Panning away will result in the line disappearing and panning toward the origin of the line (usually 0,0,0) will make it appear again. Is there a reason for this and a way around it? I have tried playing around with render settings.
The code I have included is being used in testing and draws the trace of the object as time progresses.
var traceHandle = {
/* setup() returns trace-line */
setup : function (MAX_POINTS) {
var lineGeo = new THREE.BufferGeometry();
//var MAX_POINTS = 500*10;
var positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
lineGeo.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var lineMaterial = new THREE.LineBasicMaterial({color:0x00ff00 });
var traceLine = new THREE.Line(lineGeo, lineMaterial);
scene.add(traceLine);
return traceLine;
},
/****
* updateTrace() updates and draws trace line
* Need 'index' saved globally for this
****/
updateTrace : function (traceLine, obj, timeStep, index) {
traceLine.geometry.setDrawRange( 0, timeStep );
traceLine.geometry.dynamic = true;
var positions = traceLine.geometry.attributes.position.array;
positions[index++]=obj.position.x;
positions[index++]=obj.position.y;
positions[index++]=obj.position.z;
// required after the first render
traceLine.geometry.attributes.position.needsUpdate = true;
return index;
}
};
Thanks a lot!
Likely, the bounding sphere is not defined or has radius zero. Since you are adding points dynamically, you can set:
traceLine.frustumCulled = false;
The other option is to make sure the bounding sphere is current, but given your use case, that seems too computationally expensive.
three.js r.73
I have several nested sets in Raphael JS that I want to use like layers in photoshop. That is: objects in sets may have their own transformations, and being places to a set they become relatively positioned in it. And set may have it's own transformation too.
Now it seems when a set transformation is applied, it just performs it to each element separately and with absolute position relatively the page.
With that mechanics I run into such simple problem: I have the set and the rectangle in it. Then I resize rectangle with scale(0.5,0.5,0,0); And then I want to drag the entire set. I perform dragging with set.translate(x,y). As the result I get rectangle that moves twice slower than other non-scaled items.
var rdr = this;
this.paper = Raphael(0,0,1000,1000);
this.set = this.paper.set();
this.set.push(this.paper.rect(0,0,100,100)); // non-scaled rectangle
this.set.push(this.paper.rect(0,0,100,100).scale(0.5,0.5,0,0)); // scaled rectangle
$("body").bind("mousedown.RDR",function(e) {
var ox = e.pageX;
var oy = e.pageY;
$("body").bind("mousemove.RDR",function(e) {
rdr.set.translate(e.pageX-ox,e.pageY-oy);
ox = e.pageX;
oy = e.pageY;
}).bind("mouseup.RDR",function() {
$("body").unbind("mouseup.RDR").unbind("mousemove.RDR");
});
});
How should I correct this code to make my rectangles move with the same speed?
Theoretically all that I need to move a set of objects simultaneously is a way to control the order of transformations. I haven't found built in solution so there's a little hack that inserts translation of a set "BEFORE" transformations that're already applied to elements:
Raphael.el.translateBefore = function(x,y) {
var matrix = this.matrix;
var transform = matrix.toTransformString();
transform = ("t"+x.toString()+","+y.toString()) + "," + transform;
this.transform(transform);
return this;
}
this.paper = Raphael(this.containerId,this.paperWidth,this.paperHeight);
// добавляем метод для raphael.set через жопу, не нашел нормальный способ
this.paper.set().__proto__.translateBefore = function(x,y) {
_.each(this,function(el) {
el.translateBefore(x,y);
});
return this;
}
http://raphaeljs.com/reference.html#Element.transform
// if you want you can append or prepend transformations
el.transform("...t50,50");
el.transform("s2...");