Keep objects looking at camera - camera

guys I know this question has been asked several times, several different ways, but I just can get it to work. Basically I have 2d clouds, but I want the camera to rotate around an object floating above the clouds. The problem is, when im not looking a the face of the clouds u can tell that they are 2d. Soooo i want the the clouds to "look" at the camera where ever it is. I believe my problem stems from how the cloud geometry is called on to the planes, but here take a look. I put the a lookAt function with in my animate function. I hope you can point me in the right direction at least.
Three.js rev. 70...
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 0, 100);
scene.add(camera);
controls = new THREE.OrbitControls( camera );
controls.target.copy( new THREE.Vector3( 0, 0,475) );
controls.minDistance = 50;
controls.maxDistance = 200;
controls.autoRotate = true;
controls.autoRotateSpeed = .2; // 30 seconds per round when fps is 60
controls.minPolarAngle = Math.PI/4; // radians
controls.maxPolarAngle = Math.PI/2; // radians
controls.enableDamping = true;
controls.dampingFactor = 0.25;
clock = new THREE.Clock();
cloudGeometry = new THREE.Geometry();
var texture = THREE.ImageUtils.loadTexture('img/cloud10.png', null, animate);
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
var fog = new THREE.Fog(0x4584b4, -100, 3000);
cloudMaterial = new THREE.ShaderMaterial({
uniforms: {
"map": {
type: "t",
value: texture
},
"fogColor": {
type: "c",
value: fog.color
},
"fogNear": {
type: "f",
value: fog.near
},
"fogFar": {
type: "f",
value: fog.far
},
},
vertexShader: document.getElementById('vs').textContent,
fragmentShader: document.getElementById('fs').textContent,
depthWrite: false,
depthTest: false,
transparent: true
});
var plane = new THREE.Mesh(new THREE.PlaneGeometry(64, 64));
for (var i = 0; i < 8000; i++) {
plane.position.x = Math.random() * 1000 - 500;
plane.position.y = -Math.random() * Math.random() * 200 - 15;
plane.position.z = i;
plane.rotation.z = Math.random() * Math.PI;
plane.scale.x = plane.scale.y = Math.random() * Math.random() * 1.5 + 0.5;
plane.updateMatrix();
cloudGeometry.merge(plane.geometry, plane.matrix);
}
cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
scene.add(cloud);
cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloud.position.z = -8000;
scene.add(cloud);
var radius = 100;
var xSegments = 50;
var ySegments = 50;
var geo = new THREE.SphereGeometry(radius, xSegments, ySegments);
var mat = new THREE.ShaderMaterial({
uniforms: {
lightPosition: {
type: 'v3',
value: light.position
},
textureMap: {
type: 't',
value: THREE.ImageUtils.loadTexture("img/maps/moon.jpg")
},
normalMap: {
type: 't',
value: THREE.ImageUtils.loadTexture("img/maps/normal.jpg")
},
uvScale: {
type: 'v2',
value: new THREE.Vector2(1.0, 1.0)
}
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
});
mesh = new THREE.Mesh(geo, mat);
mesh.geometry.computeTangents();
mesh.position.set(0, 50, 0);
mesh.rotation.set(0, 180, 0);
scene.add(mesh);
}
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
function animate() {
requestAnimationFrame(animate);
light.orbit(mesh.position, clock.getElapsedTime());
cloud.lookAt( camera );
controls.update(camera);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', onWindowResize, false);

just a first guess:
the lookAt function needs Vector3 as parameter. try to use camera.position in the animate function.
cloud.lookAt( camera.position );

first of all, to build 2D objects in a scene that always faces towards the camera, you should use Sprite object, so you don't have to do anything to get this effect. (and have better performance :))
Definition from THREE.org: Sprite - a sprite is a plane in an 3d scene which faces always towards the camera.
var map = THREE.ImageUtils.loadTexture( "sprite.png" );
var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
var sprite = new THREE.Sprite( material );
scene.add( sprite );
Please check this example: http://threejs.org/examples/#webgl_points_sprites

I would absolutely agree, I would use Sprite, or even Points, but then, if assign a texture to it, it will render it square-sized. My sprites are animated, and frames cannot be packed in square tiles, cause it would take a lot of space. I might make a mesh and use this lookAt function.

Related

Three.js load a gltf model and set color for it but when zooming out it is all black

Hello everyone,I have meet a strange problem which is that I loaded a gltf model in three.js and set color for it.When zooming in it has colors, but when zooming out,it is all black.And when I directly set color for it's material,it can works well.
Thank you.
here is the sample sreenphotos and code.
loadGlbModel() {
const loader = new GLTFLoader();
loader.load(
`/three/eiffel-tower.gltf`,
(gltf) => {
const geometry = gltf.scene.children[0].geometry;
const positions = geometry.attributes.position;
const count = positions.count;
geometry.setAttribute(
"color",
new THREE.BufferAttribute(new Float32Array(count * 3), 3)
);
const color = new THREE.Color();
const colors = geometry.attributes.color;
const radius = 200;
debugger;
for (let i = 0; i < count; i++) {
color.setHSL(positions.getY(i) / radius / 2, 1.0, 0.5);
colors.setXYZ(i, 1, 0, 0);
}
const material = new THREE.MeshPhongMaterial({
color: 0xffffff,
flatShading: true,
vertexColors: true,
shininess: 0,
});
const wireframeMaterial = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true,
transparent: true,
});
let mesh = new THREE.Mesh(geometry, material);
let wireframe = new THREE.Mesh(geometry, wireframeMaterial);
mesh.add(wireframe);
mesh.scale.set(0.1, 0.1, 0.1);
const redColor = new THREE.Color(1, 0, 0);
console.log(mesh);
// mesh.children[0].material.color = redColor;
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 0, 1);
this.scene.add(light);
this.scene.add(mesh);
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
(error) => {
console.error(error);
}
);
},
You are rendering the wireframe version too, which consists of lines in screen-space. As you zoom out, these lines maintain the same width in pixels, thus covering everything else.
Do you wish to render the fireframe too? If not, don't. If you do, then consider hiding it as the user zooms out.

How to get original pixel color before shader

I have an ImageLayer and a RasterSource which uses a shader to manipulate the colors of the image.
While listening to the map's pointermove I get the color of the pixel under the pointer which is the color manipulated by the shader.
How can I get the original color before it was manipulated by the shader?
const extent = [0, 0, 1024, 968]; // the image extent in pixels.
const projection = new Projection({
code: 'xkcd-image',
units: 'pixels',
extent: extent,
});
let staticImage = new Static({ // from 'ol/source/ImageStatic'
attributions: '© xkcd',
url: "./assets/map.png",
projection: projection,
imageExtent: extent
});
let imageLayer = new ImageLayer({ // from 'ol/layer/Image'
source: new RasterSource({
sources: [staticImage],
operation: function (pixels, data) {
let p = pixels[0];
let grayscale = (p[0] + p[1] + p[2]) / 3;
p[0] = grayscale;
p[1] = grayscale;
p[2] = grayscale;
return p;
}
})
});
let map; // Map from 'ol'
map.on('pointermove', (evt) => {
let pixel = evt.pixel;
let color = imageLayer.getRenderer().getDataAtPixel(pixel, evt.framestate, 1);
// color is the one manipulated by the shader
});
More code here:
https://codesandbox.io/s/raster-original-pixel-3mejh9?file=/main.js
Note: because of security restrictions I've had to comment out the shader code which turns the colors gray
Which was adapted from this example:
https://openlayers.org/en/latest/examples/static-image.html
I found a workaround by adding a duplicate RasterLayer with a no-op operation. The trick is to add the layer just after the color manipulated layer, but with an opacity of 0.005 (apparently the lowest possible value) so it's rendered but you don't see it. Then combine the grey color's alpha with the original color's RGB values:
let noopLayer = new ImageLayer({
opacity: 0.005,
source: new RasterSource({
sources: [
staticImage
],
operation: function (pixels, data) {
let p = pixels[0];
return p;
},
}),
});
const map = new Map({
layers: [
rasterLayer,
noopLayer,
],
target: 'map',
view: new View({
projection: projection,
center: getCenter(extent),
zoom: 2,
maxZoom: 8,
}),
});
map.on('pointermove', (evt) => {
const pixel = evt.pixel;
let grey = rasterLayer.getRenderer().getDataAtPixel(pixel, evt.framestate, 1);
let noop = noopLayer.getRenderer().getDataAtPixel(pixel, evt.framestate, 1);
if (grey != null) {
// [228, 228, 228, 255]
console.log('grey:', grey[0], grey[1], grey[2], grey[3]);
// [255, 255, 170, 3]
console.log('noop:', noop[0], noop[1], noop[2], noop[3]);
// [255, 255, 170, 255]
console.log('original:', noop[0], noop[1], noop[2], grey[3]);
}
});

How to change the direction of a group sprite based on a condition in Phaser 3

I'm using Phaser 3 and tried doing this:
class Bee extends Phaser.GameObjects.Sprite {
constructor(config) {
super(config.scene, config.x, config.y, 'bee');
config.scene.add.existing(this);
config.scene.physics.add.existing(this);
this.initial = [config.x, config.y];
this.x = config.x;
this.y = config.y;
this.speed = 5;
}
preUpdate() {
if (this.y < this.initial[1] - 100) {
this.speed *= -1;
}
if (this.y > this.initial[1] + 100) {
this.speed *= -1;
}
this.y += this.speed;
this.setY(this.y);
}
}
I want a Bee object to move down 100 px then move up and then repeat, but it doesn't seem to work.
In my scene I created a object:
bees = this.physics.add.group();
let bee = new Bee({scene:scene, x: 100, y:200})
bee.setScale(0.5);
bee.allowGravity = false;
bees.add(bee);
I'll have multiple Bee sprites in the game.
You could solve the up and down movement with a simple tween (https://photonstorm.github.io/phaser3-docs/Phaser.Tweens.Tween.html).
A tween changes/moves the selected property (or multiple properties) between a starting and an end position. In your case, I would use it to change the y property of the bee physic body velocity.
You can do alot with a tween, without the need to calculate speed, next position and so on. You just have to tweak the properties of the tween config, to get the desired movement/animation.
Here a working example:
(Update: Now with a few bees in the group and an 'offset' for the flight pattern)
class Bee extends Phaser.GameObjects.Sprite {
constructor(config) {
super(config.scene, config.x, config.y, 'bee');
config.scene.add.existing(this);
config.scene.physics.add.existing(this);
this.x = config.x;
this.y = config.y;
config.scene.tweens.add({
targets: this.body.velocity,
y: { from: -100, to: 100 },
ease: Phaser.Math.Easing.Quadratic.InOut,
yoyo: true,
repeat: -1,
duraton: 1000,
// just to create a interesting offset for the example
delay: Phaser.Math.Between(0,6) * 200
});
}
}
function create(){
let bees = this.physics.add.group();
for(let i=0; i < 10 ; i++){
let bee = new Bee({scene: this,
x: i * -100 ,
y: 50 });
bees.add(bee);
}
bees.setVelocity(100,0)
}
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
physics: {
default: 'arcade',
},
scene: {
create
}
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

How do I scale a group in Phaser 3

In Phaser 2 we scale a simple by setting the scale property as explained in docs:
https://phaser.io/examples/v2/groups/group-scale
But there is no equivalent in Phaser v3.
The possible url https://phaser.io/examples/v3/groups/group-scale points to nothing. And if I do:
this.enemies = this.add.group();
this.enemies.scale.set(2, 2);
It throws:
Phaser v3.19.0 (WebGL | Web Audio) https://phaser.io
indexph.js:22 Uncaught TypeError: Cannot read property 'set' of undefined
What is the appropriate form to scale a group of sprites in Phaser 3?
The code below should work, I think, But it doesn't.... it doesn't scale objects that are created from the group:
preload() {
this.load.atlas("sprites", "assets/spritesheet.png", "assets/spritesheet.json")
}
create() {
this.enemies = this.add.group({
key: 'sprites' ,
setScale: { x: 0.1, y: 0.1 }
});
this.enemies.create(60, 60, 'sprites', 'hamburguer.png');
In Phaser 3, you can scale a group by modifying the GroupConfig object passed in when you declare your group.
GroupConfig API Reference. You can see also see a live demo here.
In your case, to scale this group you should simply create it like:
this.enemies = this.add.group({
setScale: { x: 2, y: 2}
});
Alternatively, you could iterate through the group once it's created and scale each child object independently.
this.enemies = this.add.group();
this.enemies.children.iterate((child) => {
child.setScale(2, 2);
});
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600, loader: {
baseURL: 'https://raw.githubusercontent.com/nazimboudeffa/assets/master/',
crossOrigin: 'anonymous'
},
scene: {
preload: preload,
create: create
},
physics: {
default: 'arcade'
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('alien1', 'sprites/phaser-alien.png');
this.load.image('alien2', 'sprites/alien2.png');
}
function create ()
{
this.enemies1 = this.add.group();
this.enemies2 = this.add.group();
for (let i = 0; i < 64; i++)
{
let x = Phaser.Math.Between(0, 400);
let y = Phaser.Math.Between(0, 600);
this.enemy1 = this.add.image(x, y, 'alien1');
this.enemies1.add(this.enemy1);
}
for (let i = 0; i < 64; i++)
{
let x = Phaser.Math.Between(400, 800);
let y = Phaser.Math.Between(0, 600);
this.enemy2 = this.add.image(x, y, 'alien2');
this.enemies2.add(this.enemy2);
}
console.log(this.enemies1.getLength())
//console.log(this.enemies.getChildren())
console.log(this.enemies1.getChildren()[2])
for (let i = 0; i < 64; i++)
{
this.enemies1.getChildren()[i].setScale(2);
}
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.19.0/dist/phaser.js"></script>

How to find co-ordinates from google map api to display markers from database in map

I have a search page in which I am displaying properties from my database as markers in google map. Now I want to modify this search page so that any user will draw either a circle or polygon in the google map and will get properties from our database only in the selected area (drawn by user itself). I am attaching my code so far here.
The circle in the image shows the selected area and the result must display properties available in the selected region. My table contains 2 different fields for latitude and longitude of each property.
Javascript code:
<script type="text/javascript">
var drawingManager;
var all_overlays = [];
var selectedShape;
var colors = ['#1E90FF', '#FF1493', '#32CD32', '#FF8C00', '#4B0082'];
var selectedColor;
var colorButtons = {};
function clearSelection() {
if (selectedShape) {
selectedShape.setEditable(false);
selectedShape = null;
}
}
function setSelection(shape) {
clearSelection();
selectedShape = shape;
shape.setEditable(true);
selectColor(shape.get('fillColor') || shape.get('strokeColor'));
}
function deleteSelectedShape() {
if (selectedShape) {
selectedShape.setMap(null);
}
}
function deleteAllShape() {
for (var i = 0; i < all_overlays.length; i++) {
all_overlays[i].overlay.setMap(null);
}
all_overlays = [];
}
function selectColor(color) {
selectedColor = color;
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
colorButtons[currColor].style.border = currColor == color ? '2px solid #789' : '2px solid #fff';
}
// Retrieves the current options from the drawing manager and replaces the
// stroke or fill color as appropriate.
var polylineOptions = drawingManager.get('polylineOptions');
polylineOptions.strokeColor = color;
drawingManager.set('polylineOptions', polylineOptions);
var rectangleOptions = drawingManager.get('rectangleOptions');
rectangleOptions.fillColor = color;
drawingManager.set('rectangleOptions', rectangleOptions);
var circleOptions = drawingManager.get('circleOptions');
circleOptions.fillColor = color;
drawingManager.set('circleOptions', circleOptions);
var polygonOptions = drawingManager.get('polygonOptions');
polygonOptions.fillColor = color;
drawingManager.set('polygonOptions', polygonOptions);
}
function setSelectedShapeColor(color) {
if (selectedShape) {
if (selectedShape.type == google.maps.drawing.OverlayType.POLYLINE) {
selectedShape.set('strokeColor', color);
} else {
selectedShape.set('fillColor', color);
}
}
}
function makeColorButton(color) {
var button = document.createElement('span');
button.className = 'color-button';
button.style.backgroundColor = color;
google.maps.event.addDomListener(button, 'click', function () {
selectColor(color);
setSelectedShapeColor(color);
});
return button;
}
function buildColorPalette() {
var colorPalette = document.getElementById('color-palette');
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
var colorButton = makeColorButton(currColor);
colorPalette.appendChild(colorButton);
colorButtons[currColor] = colorButton;
}
selectColor(colors[0]);
}
function initialize() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 6,
center: new google.maps.LatLng(22, 77),
mapTypeId: google.maps.MapTypeId.HYBRID,
disableDefaultUI: true,
zoomControl: true
});
var polyOptions = {
strokeWeight: 0,
fillOpacity: 0.45,
editable: true
};
// Creates a drawing manager attached to the map that allows the user to draw
// markers, lines, and shapes.
drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.POLYGON,
markerOptions: {
draggable: true
},
polylineOptions: {
editable: true
},
rectangleOptions: polyOptions,
circleOptions: polyOptions,
polygonOptions: polyOptions,
map: map
});
google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
all_overlays.push(e);
if (e.type != google.maps.drawing.OverlayType.MARKER) {
// Switch back to non-drawing mode after drawing a shape.
drawingManager.setDrawingMode(null);
// Add an event listener that selects the newly-drawn shape when the user
// mouses down on it.
var newShape = e.overlay;
newShape.type = e.type;
google.maps.event.addListener(newShape, 'click', function () {
setSelection(newShape);
});
setSelection(newShape);
}
});
// Clear the current selection when the drawing mode is changed, or when the
// map is clicked.
google.maps.event.addListener(drawingManager, 'drawingmode_changed', clearSelection);
google.maps.event.addListener(map, 'click', clearSelection);
google.maps.event.addDomListener(document.getElementById('delete-button'), 'click', deleteSelectedShape);
google.maps.event.addDomListener(document.getElementById('delete-all-button'), 'click', deleteAllShape);
buildColorPalette();
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
The code above contains some events to handle the user interaction in search page to draw cirle,polygone to search property in selected region.
The display property button in the image must show property markers in the selected area.
How would I get the latitude and longitude of selected area of circle or polygon?
How Would I query my database for that range of co-ordinates?
Thanking you very much in advance for your kind help...
I suppose you have found a solution since you post this question.
However, if someone else needs an answer, this is how you can get lat / lng of a drawing polygon :
google.maps.event.addDomListener(drawingManager, 'polygoncomplete', function(polygon) {
var polygonBounds = polygon.getPath();
}
The bounds of each vertices of your polygon are now in polygonBounds, just iterate over it and do what you want with lat / lng
For a circle, you just need lat / lng of the center, and its radius
google.maps.event.addListener(drawingManager, 'circlecomplete', function(circle) {
var radius = circle.getRadius();
var center = circle.getCenter();
}
Hope it can help someone
PS : I'm using GoogleMaps v3.14, I never test with other versions.