Ammo Threejs Breakable objects debis set material after breaking in fragments - three.js

i have initially breakable object with nice texture material loaded.
After breaking in fragments lost original material and load some default material.
Any suggest?
I use code from threejs example with ammo:
Source code:
import * as THREE from "three";
import {ConvexObjectBreaker} from "../jsm/misc/ConvexObjectBreaker";
import {updatePhysics} from "./updater";
export class MagicPhysics {
// Physics variables
gravityConstant = 7.8;
collisionConfiguration;
dispatcher;
broadphase;
solver;
physicsWorld;
margin = 0.05;
convexBreaker = new ConvexObjectBreaker();
// Rigid bodies include all movable objects
rigidBodies = [];
pos = new THREE.Vector3();
quat = new THREE.Quaternion();
transformAux1;
tempBtVec3_1;
objectsToRemove = [];
// Player
ammoTmpPos;
ammoTmpQuat;
tmpTrans;
numObjectsToRemove = 0;
impactPoint = new THREE.Vector3();
impactNormal = new THREE.Vector3();
// kinekt type of movement
kMoveDirection = {left: 0, right: 0, forward: 0, back: 0};
// velocity type of movement
moveDirection = {left: 0, right: 0, forward: 0, back: 0};
tmpPos = new THREE.Vector3();
tmpQuat = new THREE.Quaternion();
constructor(options) {
console.log("MagicPhysics =>", options)
this.updatePhysics = updatePhysics.bind(this);
this.config = options.config;
}
initPhysics() {
// Physics configuration
this.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);
this.broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
this.physicsWorld = new Ammo.btDiscreteDynamicsWorld(
this.dispatcher,
this.broadphase,
solver,
this.collisionConfiguration
);
this.physicsWorld.setGravity(new Ammo.btVector3(0, -this.gravityConstant, 0));
this.transformAux1 = new Ammo.btTransform();
this.tempBtVec3_1 = new Ammo.btVector3(0, 0, 0);
}
createRigidBody(object, physicsShape, mass, pos, quat, vel, angVel) {
if(pos) {
object.position.copy(pos);
} else {
pos = object.position;
}
if(quat) {
object.quaternion.copy(quat);
} else {
quat = object.quaternion;
}
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(
new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
);
const motionState = new Ammo.btDefaultMotionState(transform);
const localInertia = new Ammo.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(
mass,
motionState,
physicsShape,
localInertia
);
const body = new Ammo.btRigidBody(rbInfo);
body.setFriction(0.5);
if(vel) {
body.setLinearVelocity(new Ammo.btVector3(vel.x, vel.y, vel.z));
}
if(angVel) {
body.setAngularVelocity(
new Ammo.btVector3(angVel.x, angVel.y, angVel.z)
);
}
object.userData.physicsBody = body;
object.userData.collided = false;
this.scene.add(object);
if(mass > 0) {
this.rigidBodies.push(object);
// Disable deactivation
body.setActivationState(4);
}
this.physicsWorld.addRigidBody(body);
return body;
}
createConvexHullPhysicsShape(coords) {
const shape = new Ammo.btConvexHullShape();
for(let i = 0, il = coords.length;i < il;i += 3) {
this.tempBtVec3_1.setValue(coords[i], coords[i + 1], coords[i + 2]);
const lastOne = i >= il - 3;
shape.addPoint(this.tempBtVec3_1, lastOne);
}
return shape;
}
createParalellepipedWithPhysics(sx, sy, sz, mass, pos, quat, material) {
const object = new THREE.Mesh(
new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1),
material
);
const shape = new Ammo.btBoxShape(
new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5)
);
shape.setMargin(this.margin);
this.createRigidBody(object, shape, mass, pos, quat);
return object;
}
createDebrisFromBreakableObject(object) {
object.castShadow = true;
object.receiveShadow = true;
const shape = this.createConvexHullPhysicsShape(
object.geometry.attributes.position.array
);
shape.setMargin(this.margin);
const body = this.createRigidBody(
object,
shape,
object.userData.mass,
null,
null,
object.userData.velocity,
object.userData.angularVelocity
);
// Set pointer back to the three object only in the debris objects
const btVecUserData = new Ammo.btVector3(0, 0, 0);
btVecUserData.threeObject = object;
body.setUserPointer(btVecUserData);
}
removeDebris(object) {
this.scene.remove(object);
this.physicsWorld.removeRigidBody(object.userData.physicsBody);
}
}

Related

how do i make FlxAsyncLoop work for making a progress bar in HaxeFlixel?

so, i tried to make an async loop for my game (to make a progress bar) and the game crashes when the state loads...
i tried to change the loops position so they don't collide and all of the code is from the FlxAsyncLoop demo but with other variables and some other changes.
here's the code:
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.addons.util.FlxAsyncLoop;
import flixel.group.FlxGroup;
import flixel.math.FlxPoint;
import flixel.math.FlxVelocity;
import flixel.text.FlxText;
import flixel.ui.FlxBar;
import flixel.util.FlxColor;
class PlayState extends FlxState
{
public static var player:FlxSprite;
var ground:FlxSprite;
var axe:FlxSprite;
var wood:FlxSprite;
var base:FlxSprite;
var txt:FlxText;
var playerPos:FlxPoint;
var enemy:FlxSprite;
var move = false;
var progressR:FlxGroup;
var progressE:FlxGroup;
var resourceGroup:FlxGroup;
var enemyGroup:FlxGroup;
var maxItems:Int = 100;
var bar1:FlxBar;
var bartxt1:FlxText;
var bar2:FlxBar;
var bartxt2:FlxText;
var loopR:FlxAsyncLoop;
var loopE:FlxAsyncLoop;
public function addR()
{
var wood = new FlxSprite(FlxG.random.int(100, 2500), FlxG.random.int(100, 1800));
wood.makeGraphic(40, 100, FlxColor.BROWN);
add(wood);
bar1.value = (resourceGroup.members.length / maxItems) * 100;
bartxt1.text = "Loading resources... " + resourceGroup.members.length + " / " + maxItems;
bartxt1.screenCenter();
}
public function addE()
{
var enemy = new FlxSprite(FlxG.random.int(300, 2500), FlxG.random.int(300, 1800));
enemy.makeGraphic(32, 32, FlxColor.RED);
add(enemy);
bar2.value = (enemyGroup.members.length / maxItems) * 100;
bartxt2.text = "Loading enemys... " + enemyGroup.members.length + " / " + maxItems;
bartxt2.screenCenter();
}
override public function create()
{
super.create();
resourceGroup = new FlxGroup(maxItems);
enemyGroup = new FlxGroup(maxItems);
loopR = new FlxAsyncLoop(maxItems, addR);
loopE = new FlxAsyncLoop(maxItems, addE);
FlxG.camera.zoom = 0.5;
playerPos = FlxPoint.get();
ground = new FlxSprite(0, 0);
ground.makeGraphic(2500, 1800, FlxColor.GREEN);
add(ground);
player = new FlxSprite(100, 100);
player.loadGraphic(AssetPaths.n1__png);
axe = new FlxSprite(player.x + 60, player.y);
axe.loadGraphic(AssetPaths.axeR__png);
camera = new FlxCamera(0, 0, 2500, 1800);
camera.bgColor = FlxColor.TRANSPARENT;
camera.setScrollBoundsRect(0, 0, ground.width, ground.height);
FlxG.cameras.reset(camera);
camera.target = player;
if (FlxG.collide(wood, player))
FlxObject.separate(wood, player);
if (FlxG.collide(wood, enemy))
FlxObject.separate(wood, enemy);
if (FlxG.collide(enemy, player))
FlxObject.separate(enemy, player);
progressR = new FlxGroup();
bar1 = new FlxBar(0, 0, LEFT_TO_RIGHT, FlxG.width, 50, null, "", 0, 100, true);
bar1.value = 0;
bar1.screenCenter();
progressR.add(bar1);
bartxt1 = new FlxText(0, 0, FlxG.width, "Loading resources... 0 / " + maxItems);
bartxt1.setFormat(null, 28, FlxColor.WHITE, CENTER, OUTLINE);
bartxt1.screenCenter();
progressR.add(bartxt1);
progressE = new FlxGroup();
bar2 = new FlxBar(0, 0, LEFT_TO_RIGHT, FlxG.width, 50, null, "", 0, 100, true);
bar2.value = 0;
bar2.screenCenter();
progressE.add(bar2);
bartxt2 = new FlxText(0, 0, FlxG.width, "Loading enemys... 0 / " + maxItems);
bartxt2.setFormat(null, 28, FlxColor.WHITE, CENTER, OUTLINE);
bartxt2.screenCenter();
progressE.add(bartxt2);
resourceGroup.visible = false;
resourceGroup.active = false;
enemyGroup.visible = false;
enemyGroup.active = false;
add(progressR);
add(progressE);
add(resourceGroup);
add(enemyGroup);
add(loopR);
}
override public function update(elapsed:Float)
{
super.update(elapsed);
if (FlxG.keys.pressed.LEFT && move)
{
player.x -= 5;
axe.loadGraphic(AssetPaths.axeL__png);
}
if (FlxG.keys.pressed.RIGHT && move)
{
player.x += 5;
axe.loadGraphic(AssetPaths.axeR__png);
}
if (FlxG.keys.pressed.UP && move)
{
player.y -= 5;
}
if (FlxG.keys.pressed.DOWN && move)
{
player.y += 5;
}
axe.x = player.x + 60;
axe.y = player.y;
playerPos = FlxPoint.get();
playerPos = player.getMidpoint();
FlxVelocity.moveTowardsPoint(enemy, playerPos, 70);
if (!loopR.started)
{
loopR.start();
}
else
{
if (loopR.finished)
{
resourceGroup.visible = true;
progressR.visible = false;
resourceGroup.active = true;
progressR.active = false;
loopR.kill();
loopR.destroy();
add(loopE);
loopE.start();
}
}
if (loopE.finished)
{
move = true;
add(player);
add(axe);
enemyGroup.visible = true;
progressE.visible = false;
enemyGroup.active = true;
progressE.active = false;
loopE.kill();
loopE.destroy();
}
}
}
i'm showing everything because of the functions and other things that can make this problem
(my english is bad sorry if i miss something...)
Without knowing the exact error, I suspect that the problem is when you call
if (FlxG.collide(wood, player))
FlxObject.separate(wood, player);
if (FlxG.collide(wood, enemy))
FlxObject.separate(wood, enemy);
if (FlxG.collide(enemy, player))
FlxObject.separate(enemy, player);
Inside your create function - wood and enemy are declared in other functions and do not exist there.
There are a couple of things I would do differently here:
(Note: I didn't TEST this code, so there may still be problems, but this might help get you pointed in the right direction...)
class PlayState extends FlxState
{
public static var player:FlxSprite;
var ground:FlxSprite;
var axe:FlxSprite;
var base:FlxSprite;
var txt:FlxText;
var playerPos:FlxPoint;
var move:Bool = false;
var progressR:FlxGroup;
var progressE:FlxGroup;
var resourceGroup:FlxTypedGroup<FlxSprite>;
var enemyGroup:FlxTypedGroup<FlxSprite>;
var maxItems:Int = 100;
var bar1:FlxBar;
var bartxt1:FlxText;
var bar2:FlxBar;
var bartxt2:FlxText;
var loopR:FlxAsyncLoop;
var loopE:FlxAsyncLoop;
public function addR()
{
var wood = new FlxSprite(FlxG.random.int(100, 2500), FlxG.random.int(100, 1800));
wood.makeGraphic(40, 100, FlxColor.BROWN);
resourceGroup.add(wood);
bar1.value = (resourceGroup.members.length / maxItems) * 100;
bartxt1.text = "Loading resources... " + resourceGroup.members.length + " / " + maxItems;
bartxt1.screenCenter();
FlxG.collide(player, resourceGroup) // should not need to call `seperate` - it's automatic when using `collide` vs `overlap`
}
public function addE()
{
var enemy = new FlxSprite(FlxG.random.int(300, 2500), FlxG.random.int(300, 1800));
enemy.makeGraphic(32, 32, FlxColor.RED);
enemyGroup.add(enemy);
FlxG.collide(enemyGroup, resourceGroup)
FlxG.collide(player, resourceGroup)
bar2.value = (enemyGroup.members.length / maxItems) * 100;
bartxt2.text = "Loading enemys... " + enemyGroup.members.length + " / " + maxItems;
bartxt2.screenCenter();
}
override public function create()
{
super.create();
resourceGroup = new FlxTypedGroup<FlxSprite>(maxItems);
enemyGroup = new FlxTypedGroup<FlxSprite>(maxItems);
loopR = new FlxAsyncLoop(maxItems, addR);
loopE = new FlxAsyncLoop(maxItems, addE);
FlxG.camera.zoom = 0.5;
playerPos = FlxPoint.get();
ground = new FlxSprite(0, 0);
ground.makeGraphic(2500, 1800, FlxColor.GREEN);
add(ground);
player = new FlxSprite(100, 100);
player.loadGraphic(AssetPaths.n1__png);
axe = new FlxSprite(player.x + 60, player.y);
axe.loadGraphic(AssetPaths.axeR__png);
camera = new FlxCamera(0, 0, 2500, 1800);
camera.bgColor = FlxColor.TRANSPARENT;
camera.setScrollBoundsRect(0, 0, ground.width, ground.height);
FlxG.cameras.reset(camera);
camera.target = player;
progressR = new FlxGroup();
bar1 = new FlxBar(0, 0, LEFT_TO_RIGHT, FlxG.width, 50, null, "", 0, 100, true);
bar1.value = 0;
bar1.screenCenter();
progressR.add(bar1);
bartxt1 = new FlxText(0, 0, FlxG.width, "Loading resources... 0 / " + maxItems);
bartxt1.setFormat(null, 28, FlxColor.WHITE, CENTER, OUTLINE);
bartxt1.screenCenter();
progressR.add(bartxt1);
progressE = new FlxGroup();
bar2 = new FlxBar(0, 0, LEFT_TO_RIGHT, FlxG.width, 50, null, "", 0, 100, true);
bar2.value = 0;
bar2.screenCenter();
progressE.add(bar2);
bartxt2 = new FlxText(0, 0, FlxG.width, "Loading enemys... 0 / " + maxItems);
bartxt2.setFormat(null, 28, FlxColor.WHITE, CENTER, OUTLINE);
bartxt2.screenCenter();
progressE.add(bartxt2);
resourceGroup.visible = false;
resourceGroup.active = false;
enemyGroup.visible = false;
enemyGroup.active = false;
add(progressR);
add(progressE);
add(resourceGroup);
add(enemyGroup);
add(loopR);
}
public function gamePlay(elapsed:Float):Void
{
if (FlxG.keys.pressed.LEFT && move)
{
player.x -= 5; // why +/- position and not .velocity?
// THIS is probably not right - you want to use a sprite sheet with animations and call axe.play("left") or something...
axe.loadGraphic(AssetPaths.axeL__png);
}
if (FlxG.keys.pressed.RIGHT && move)
{
player.x += 5;
axe.loadGraphic(AssetPaths.axeR__png);
}
if (FlxG.keys.pressed.UP && move)
{
player.y -= 5;
}
if (FlxG.keys.pressed.DOWN && move)
{
player.y += 5;
}
axe.x = player.x + 60;
axe.y = player.y;
playerPos = FlxPoint.get();
playerPos = player.getMidpoint();
for (e in enemyGroup)
{
FlxVelocity.moveTowardsPoint(e, playerPos, 70);
}
}
public function loading(elapsed:Float):Void
{
if (!loopR.started)
{
loopR.start();
}
else if (loopR.finished)
{
resourceGroup.visible = true;
progressR.visible = false;
resourceGroup.active = true;
progressR.active = false;
loopR.kill();
loopR.destroy();
add(loopE);
loopE.start();
}
else if (loopE.finished)
{
move = true;
add(player);
add(axe);
enemyGroup.visible = true;
progressE.visible = false;
enemyGroup.active = true;
progressE.active = false;
loopE.kill();
loopE.destroy();
}
}
override public function update(elapsed:Float)
{
super.update(elapsed);
if (!move)
{
loading(elapsed);
}
else
{
gamePlay(elapsed);
}
}
}

Cannon js move physical body with animation

I'm trying to figure this out for a while now and I just can't find a solution.
I'm working with three JS and the Cannon Library.
I created a simple object/ mesh with a tween animation. With the Cannon Library I added a physical body to the mesh.
I put an animation an the mesh so it changes the rotation but the rotation of the physical body just doesn't change at all.
second position
First position
This is where I define the physics:
class Physics {
constructor() {
this.world = new CANNON.World();
this.stepSize = 0;
this.timeToGo = 0;
this.visualObjects = [];
this.physicalBodies = [];
}
addPair(visualObject, body) {
this.visualObjects.push(visualObject);
this.physicalBodies.push(body);
}
initialize(gravityX, gravityY, gravityZ, stepsize, addfloor) {
this.world.gravity.set(gravityX, gravityY, gravityZ);
this.world.broadphase = new CANNON.NaiveBroadphase();
his.stepSize = stepsize;
if (addfloor) {
var floor = new CANNON.Body({
shape: new CANNON.Plane(),
mass: 0
});
floor.quaternion.setFromAxisAngle(new CANNON.Vec3(1, -0, 0), -Math.PI / 2);
floor.position.y = -32;
this.world.addBody(floor);
}
}
update(delta) {
// Step physics world forward
this.timeToGo += delta;
while (this.timeToGo >= this.stepSize) {
this.world.step(this.stepSize);
this.timeToGo -= this.stepSize;
}
// Copy transformations
for (var i = 0; i < this.visualObjects.length; i++) {
this.visualObjects[i].position.copy(this.physicalBodies[i].position);
this.visualObjects[i].quaternion.copy(this.physicalBodies[i].quaternion);
}
}
getWorld() {
return this.world;
}
addStandPhysical(visualObject, mass){
var shapeAblage = new CANNON.Box(new CANNON.Vec3(0.5*2,0.5*8,0.5*40));
var shapeMittelstrebe = new CANNON.Box(new CANNON.Vec3(0.5*10,0.5*1,0.5*100));
var shapeAblage3 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeAblage4 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeFuss5 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeMittelding = new CANNON.Cylinder(2,2,25,32)
var body = new CANNON.Body({ mass: mass });
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage1
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage2
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage3
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage4
body.addShape(shapeMittelstrebe, new CANNON.Vec3( 1.75, 0, 0), new CANNON.Quaternion().setFromEuler(35* DEG_TO_RAD, 0, 0, "XYZ"));
body.addShape(shapeMittelstrebe, new CANNON.Vec3( -1.75, 0, 0), new CANNON.Quaternion().setFromEuler(-35* DEG_TO_RAD, 0, 0, "XYZ"));
body.position.copy(visualObject.position);
body.quaternion.copy(visualObject.quaternion);
this.world.addBody(body);
this.addPair(visualObject, body);
}
}
And this is basically my main:
function main() {
scene = new THREE.Scene();
physics = new Physics();
physics.initialize(0, -200, 0, 1 / 120, floor = false); //Hier mit der Stepsize vielleicht noch ein wenig experimentieren und herausfinden, was sie macht
physicsVisualDebugger = new THREE.CannonDebugRenderer(scene, physics.getWorld());
var stand = new Stand();
physics.addStandPhysical(stand,3);
scene.add(stand);
var clock = new THREE.Clock();
function mainLoop() {
stats.begin();
var delta = clock.getDelta();
physics.update(delta);
physicsVisualDebugger.update();
stand.animations.forEach(function (animation) {
animation.update(delta);
});
TWEEN.update();
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(mainLoop);
}
}
So long question short: how do I get the physical Bodies to move with my animation of the meshes to the ground?
I consider but maybe I am wrong, that your two arrays
this.visualObjects, this.physicalBodies
have not the same length. So maybe the update loop do not copy right the positions and quaternions. Better use the loop this way:
world.bodies.forEach( function(body) { ... }
to get looped exact the bodies.

THREE.JS Cannot read property 'matrixWorld' of undefined

When I create a sprite, OrbitControls works perfectly.
But when I switch to FirstPersonControls, everything freezes and the console gives this error:
Uncaught TypeError: Cannot read property 'matrixWorld' of undefined
at Sprite.raycast (three.js:26051)
at intersectObject (three.js:44583)
at Raycaster.intersectObjects (three.js:44658)
at animate (mainview.php?project_id=46&tipo=1:936)
Raycasting code:
if (firstorbit) {
if (l % 5 == 0) {
var vector = new THREE.Vector3(0, -1, 0);
vector = camera.localToWorld(vector);
vector.sub(camera.position);
var raycasterGround = new THREE.Raycaster(camera.position, vector);
var intersectsGround = raycasterGround.intersectObjects(scene.children, true);
if (intersectsGround.length > 0) {
if (intersectsGround[0].distance < 1.21) {
camera.position.y = (camera.position.y + (1.21 - intersectsGround[0].distance));
}
if (intersectsGround[0].distance > 1.36 && intersectsGround[0].distance < 1.45) {
camera.position.y = (camera.position.y - (intersectsGround[0].distance - 1.215));
}
}
}
if (l % 3 == 0) {
var vector1 = new THREE.Vector3(0, 0, -1);
vector1 = camera.localToWorld(vector1);
vector1.sub(camera.position);
var raycasterFront = new THREE.Raycaster(camera.position, vector1);
var intersectsFront = raycasterFront.intersectObjects(scene.children, true);
if (intersectsFront.length > 0) {
if (intersectsFront[0].distance < 0.3) {
controls.moveForward = false;
controls.w = false;
} else {
controls.w = true;
}
} else controls.w = true;
}
}
Raycasting code that I use on mouse click to measure something:
function getIntersections( event ) {
var vectorMouse = new THREE.Vector2();
vectorMouse.set(
mouse.x,
mouse.y );
var raycasterClick = new THREE.Raycaster();
raycasterClick.setFromCamera( vectorMouse, camera );
var intersectsClick = raycasterClick.intersectObjects( scene.children,true );
return intersectsClick;
}
I am not sure if the problem is with the sprite or with my raycasting code. But when I remove the sprite, everything works perfectly. And when I remove the Raycasting code, everything also works perfectly.

Add a texture on a sphere by Urhosharp

I'm using Xamarin.Forms + Urhosharp, I've got a problem to set texture from an image on a sphere. The problem is that texture.Load or texture.SetData always returns false. I did try different methods like SetData, Load, resize texture and image (to a power of 2 number) and ... but none of them worked. Here is my code:
private async void CreateScene()
{
Input.SubscribeToTouchEnd(OnTouched);
_scene = new Scene();
_octree = _scene.CreateComponent<Octree>();
_plotNode = _scene.CreateChild();
var baseNode = _plotNode.CreateChild().CreateChild();
var plane = baseNode.CreateComponent<StaticModel>();
plane.Model = CoreAssets.Models.Sphere;
var cameraNode = _scene.CreateChild();
_camera = cameraNode.CreateComponent<Camera>();
cameraNode.Position = new Vector3(10, 15, 10) / 1.75f;
cameraNode.Rotation = new Quaternion(-0.121f, 0.878f, -0.305f, -0.35f);
Node lightNode = cameraNode.CreateChild();
var light = lightNode.CreateComponent<Light>();
light.LightType = LightType.Point;
light.Range = 100;
light.Brightness = 1.3f;
int size = 3;
baseNode.Scale = new Vector3(size * 1.5f, 1, size * 1.5f);
var imageStream = await new HttpClient().GetStreamAsync("some 512 * 512 jpg image");
var ms = new MemoryStream();
imageStream.CopyTo(ms);
var image = new Image();
var isLoaded = image.Load(new MemoryBuffer(ms));
if (!isLoaded)
{
throw new Exception();
}
var texture = new Texture2D();
//var isTextureLoaded = texture.Load(new MemoryBuffer(ms.ToArray()));
var isTextureLoaded = texture.SetData(image);
if (!isTextureLoaded)
{
throw new Exception();
}
var material = new Material();
material.SetTexture(TextureUnit.Diffuse, texture);
material.SetTechnique(0, CoreAssets.Techniques.Diff, 0, 0);
plane.SetMaterial(material);
try
{
await _plotNode.RunActionsAsync(new EaseBackOut(new RotateBy(2f, 0, 360, 0)));
}
catch (OperationCanceledException) { }
}
Please help!
To Create a material from a 2D Texture, you can use Material.FromImage .
Refer following documentation for more detail.
model.SetMaterial(Material.FromImage("earth.jpg"));
https://developer.xamarin.com/api/type/Urho.Material/
https://developer.xamarin.com/api/member/Urho.Material.FromImage/p/System.String/
private async void CreateScene()
{
_scene = new Scene();
_octree = _scene.CreateComponent<Octree>();
_plotNode = _scene.CreateChild();
var baseNode = _plotNode.CreateChild().CreateChild();
var plane = _plotNode.CreateComponent<StaticModel>();
plane.Model = CoreAssets.Models.Sphere;
plane.SetMaterial(Material.FromImage("earth.jpg"));
var cameraNode = _scene.CreateChild();
_camera = cameraNode.CreateComponent<Camera>();
cameraNode.Position = new Vector3(10, 15, 10) / 1.75f;
cameraNode.Rotation = new Quaternion(-0.121f, 0.878f, -0.305f, -0.35f);
Node lightNode = cameraNode.CreateChild();
var light = lightNode.CreateComponent<Light>();
light.LightType = LightType.Point;
light.Range = 100;
light.Brightness = 1.3f;
int size = 3;
baseNode.Scale = new Vector3(size * 1.5f, 1, size * 1.5f);
Renderer.SetViewport(0, new Viewport(_scene, _camera, null));
try
{
await _plotNode.RunActionsAsync(new EaseBackOut(new RotateBy(2f, 0, 360, 0)));
}
catch (OperationCanceledException) { }
}

Three.js - can I detect geometry 'islands' when importing?

I'm importing .3DS models into Blender 2.72b, then exporting them with the Three.js import/export addon. The models have multiple geometry 'islands' (separate groups of connected faces and vertices), each with its own material. I'd like to be able to pair each material with its corresponding island, without having to create separate THREE.Geometry objects. After some digging, I found this question which suggests using a THREE.MeshFaceMaterial to achieve multiple materials for one object. The only problem is that the geometry in that example is a simple cube, whereas my models have hundreds of faces spread across 2-5 islands.
Does Three.js have functionality for identifying geometry 'islands' in a mesh?
No. three.js does not have functionality for identifying geometry 'islands' in a mesh.
When using MeshFaceMaterial, WebGLRenderer breaks the geometry into chunks anyway -- one chunk for each material. It does that because WebGL supports one shader per geometry.
I would not merge all your geometries, and then use MeshFaceMaterial, only to have the renderer break the single geometry apart.
You can merge geometries that share a single material if you wish.
three.js r.69
I tried with a function but still is not accurate, it produce more geometries than non connected geometries:
If somebody could have a look on it, it would be grate.
function groupGeometryIntoNonConnectedGeometries(geometry) {
const material = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
vertexColors: THREE.VertexColors
});
let geometryArray = [];
const indexArray = geometry.index.array;
const positionArray = geometry.attributes.position.array;
const positionCount = geometry.attributes.position.count;
const color = new THREE.Vector3(geometry.attributes.color.array[0], geometry.attributes.color.array[1], geometry.attributes.color.array[2]);
const totalTriangles = indexArray.length / 3;
let geometryCount = 0;
let indexValueAlreadyVisited = new Uint8Array(indexArray.length);
let structure = [];
/*
* indexValue: {
* child: [ [indexval0, indexval1], [] ],
* parent: null
* }
*/
// Initialize Structure:
for (var vextexIdx=0; vextexIdx<positionCount; vextexIdx++) {
structure[vextexIdx] = {
child: [],
parent: null
}
}
for (idx=0; idx<totalTriangles; idx++) {
const geoIndex1 = indexArray[idx*3];
const geoIndex2 = indexArray[idx*3+1];
const geoIndex3 = indexArray[idx*3+2];
const triangleIndexVertexArray = [ geoIndex1, geoIndex2, geoIndex3 ].sort(function(a, b) {
return a - b;
});
structure[ triangleIndexVertexArray[0] ].child.push(triangleIndexVertexArray[1], triangleIndexVertexArray[2]);
structure[ triangleIndexVertexArray[1] ].parent = triangleIndexVertexArray[0];
structure[ triangleIndexVertexArray[2] ].parent = triangleIndexVertexArray[0];
}
let count = 0;
let currentCount = 0;
let geometryStructureArray = [];
for (let strIdx=0; strIdx<structure.length; strIdx++) {
if (structure[strIdx].parent == null) {
currentCount = count;
geometryStructureArray[currentCount] = {
name: "G_" + currentCount,
indexMap: {},
currentIndex: 0,
indexArray: [],
positionArray: [],
colorArray: []
};
count += 1;
}
if (structure[strIdx].child.length > 0) {
const childLen = structure[strIdx].child.length / 2;
for (let childIdx=0; childIdx<childLen; childIdx++) {
const vertexIndex0 = strIdx;
const vertexIndex1 = structure[strIdx].child[childIdx*2];
const vertexIndex2 = structure[strIdx].child[childIdx*2+1];
const v0 = new THREE.Vector3( positionArray[strIdx*3], positionArray[strIdx*3+1], positionArray[strIdx*3+2] );
const v1 = new THREE.Vector3( positionArray[vertexIndex1*3], positionArray[vertexIndex1*3+1], positionArray[vertexIndex1*3+2] );
const v2 = new THREE.Vector3( positionArray[vertexIndex2*3], positionArray[vertexIndex2*3+1], positionArray[vertexIndex2*3+2] );
// check vertex0
if (geometryStructureArray[currentCount].indexMap[vertexIndex0] == undefined) {
geometryStructureArray[currentCount].indexMap[vertexIndex0] = geometryStructureArray[currentCount].currentIndex;
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].currentIndex);
geometryStructureArray[currentCount].positionArray.push(v0.x, v0.y, v0.z);
geometryStructureArray[currentCount].colorArray.push(color.x, color.y, color.z);
geometryStructureArray[currentCount].currentIndex += 1;
} else {
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].indexMap[vertexIndex0]);
}
// check vertex1
if (geometryStructureArray[currentCount].indexMap[vertexIndex1] == undefined) {
geometryStructureArray[currentCount].indexMap[vertexIndex1] = geometryStructureArray[currentCount].currentIndex;
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].currentIndex);
geometryStructureArray[currentCount].positionArray.push(v1.x, v1.y, v1.z);
geometryStructureArray[currentCount].colorArray.push(color.x, color.y, color.z);
geometryStructureArray[currentCount].currentIndex += 1;
} else {
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].indexMap[vertexIndex1]);
}
// check vertex1
if (geometryStructureArray[currentCount].indexMap[vertexIndex2] == undefined) {
geometryStructureArray[currentCount].indexMap[vertexIndex2] = geometryStructureArray[currentCount].currentIndex;
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].currentIndex);
geometryStructureArray[currentCount].positionArray.push(v2.x, v2.y, v2.z);
geometryStructureArray[currentCount].colorArray.push(color.x, color.y, color.z);
geometryStructureArray[currentCount].currentIndex += 1;
} else {
geometryStructureArray[currentCount].indexArray.push(geometryStructureArray[currentCount].indexMap[vertexIndex2]);
}
}
}
}
// Convert to geometryArray:
const geometryStructureArrayLen = geometryStructureArray.length;
const object3d = new THREE.Object3D();
for (let geoIdx=0; geoIdx<geometryStructureArrayLen; geoIdx++) {
const geo = new THREE.BufferGeometry();
geo.name = "G_" + geoIdx;
const geoPositions = new Float32Array(geometryStructureArray[geoIdx].positionArray);
const geoColors = new Float32Array(geometryStructureArray[geoIdx].colorArray);
const geoIndices = new Uint32Array(geometryStructureArray[geoIdx].indexArray);
//console.log(geoIdx, "geoPositions: ", geoPositions);
//console.log(geoIdx, "geoColors: ", geoColors);
//console.log(geoIdx, "geoIndices: ", geoIndices);
geo.index = new THREE.BufferAttribute(geoIndices, 1, false);
geo.attributes.position = new THREE.BufferAttribute(geoPositions, 3, false);
geo.attributes.color = new THREE.BufferAttribute(geoColors, 3, true);
geo.computeBoundingSphere();
geo.computeBoundingBox();
const mesh = new THREE.Mesh(geo, material);
mesh.name = "M_" + geoIdx;
object3d.add(mesh);
}
//return [structure, geometryStructureArray, object3d, count];
return object3d;
}
Best regards
This is I think the correct way:
function unmergeGeometryArray(geometry) {
// Asumptions:
// geometry is BufferGeometry
// The geometry has no index duplicates (2 equal positions with different index) neither empty triangles, the geometry has been processed with mergeVertices function
// normal attribute is discarded, can be recomputed after, only color and position attributes are taken into account
const material = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
vertexColors: THREE.VertexColors
});
const indexArray = geometry.index.array;
const positionArray = geometry.attributes.position.array;
const positionCount = geometry.attributes.position.count;
const totalTriangles = indexArray.length / 3;
let triangleVisitedArray = new Uint8Array(totalTriangles);
let indexVisitedArray = new Uint8Array(positionCount);
let indexToTriangleIndexMap = [];
let missingVertices = positionCount;
let missingTriangles = totalTriangles;
// Functions:
function computeTrianglesRecursive(index, out){
//console.log("- start of computeTriangles with index:", index);
if (indexVisitedArray[index] === 1 || missingVertices === 0 || missingTriangles === 0) {
return;
}
indexVisitedArray[index] = 1;
missingVertices -= 1;
let triangleIndexArray = indexToTriangleIndexMap[index];
for(let i=0; i<indexToTriangleIndexMap[index].length; i++) {
let triangleIndex = indexToTriangleIndexMap[index][i];
if (triangleVisitedArray[triangleIndex] === 0) {
triangleVisitedArray[triangleIndex] = 1
missingTriangles -= 1;
//console.log("-- index: ", index, "; i: ", i, "; triangleIndex: ", triangleIndex);
out.push(triangleIndex);
childIndex1 = indexArray[triangleIndex*3+1];
computeTriangles(childIndex1, out);
childIndex2 = indexArray[triangleIndex*3+2];
computeTriangles(childIndex2, out);
}
}
}
function computeTriangles(indexTocheck){
let out = [];
let startIndex = indexTocheck;
let indexToCheckArray = [indexTocheck];
let i = 0;
while (i<indexToCheckArray.length) {
let index = indexToCheckArray[i];
if (indexVisitedArray[index] == 0) {
indexVisitedArray[index] = 1;
missingVertices -= 1;
let triangleIndexArray = indexToTriangleIndexMap[index];
for(let j=0; j<indexToTriangleIndexMap[index].length; j++) {
let triangleIndex = indexToTriangleIndexMap[index][j];
if (triangleVisitedArray[triangleIndex] === 0) {
triangleVisitedArray[triangleIndex] = 1;
missingTriangles -= 1;
out.push(triangleIndex);
let rootIndex = indexArray[triangleIndex*3];
let child1Index = indexArray[triangleIndex*3+1];
let child2Index = indexArray[triangleIndex*3+2];
if (indexToCheckArray.indexOf(rootIndex) === -1) {
indexToCheckArray.push(rootIndex);
}
if (indexToCheckArray.indexOf(child1Index) === -1) {
indexToCheckArray.push(child1Index);
}
if (indexToCheckArray.indexOf(child2Index) === -1) {
indexToCheckArray.push(child2Index);
}
}
}
}
i +=1;
}
return out;
}
// In the first loop we reorder indices asc order + generate map
for (triangleIndex=0; triangleIndex<totalTriangles; triangleIndex++) {
const geoIndex1 = indexArray[triangleIndex*3];
const geoIndex2 = indexArray[triangleIndex*3+1];
const geoIndex3 = indexArray[triangleIndex*3+2];
const triangleIndexVertexArray = [ geoIndex1, geoIndex2, geoIndex3 ].sort(function(a, b) {
return a - b;
});
if (indexToTriangleIndexMap[geoIndex1] === undefined) {
indexToTriangleIndexMap[geoIndex1] = [triangleIndex];
} else {
indexToTriangleIndexMap[geoIndex1].push(triangleIndex);
}
if (indexToTriangleIndexMap[geoIndex2] === undefined) {
indexToTriangleIndexMap[geoIndex2] = [triangleIndex];
} else {
indexToTriangleIndexMap[geoIndex2].push(triangleIndex);
}
if (indexToTriangleIndexMap[geoIndex3] === undefined) {
indexToTriangleIndexMap[geoIndex3] = [triangleIndex];
} else {
indexToTriangleIndexMap[geoIndex3].push(triangleIndex);
}
//indexArray[triangleIndex*3] = triangleIndexVertexArray[0];
//indexArray[triangleIndex*3+1] = triangleIndexVertexArray[1];
//indexArray[triangleIndex*3+2] = triangleIndexVertexArray[2];
}
let geometryTriangleArray = [];
let index = 0;
while (index<indexToTriangleIndexMap.length && missingVertices>0 && missingTriangles>0){
let out = [];
if (indexVisitedArray[index] === 0) {
out = computeTriangles(index);
}
if (out.length > 0) {
geometryTriangleArray.push(out);
}
index += 1;
}
let geometryArray = [];
for (let i=0; i<geometryTriangleArray.length; i++) {
let out = {
positionArray: [],
colorArray: [],
indexArray: [],
indexMap: [],
currentIndex: 0
}
let triangleArray = geometryTriangleArray[i];
for (let j=0; j<triangleArray.length; j++) {
let triangleIndex = triangleArray[j];
let rootIndex = indexArray[triangleIndex*3];
if (out.indexMap[rootIndex] === undefined) {
out.indexMap[rootIndex] = out.currentIndex;
// add vertex position and color
out.positionArray.push(
geometry.attributes.position.array[rootIndex*3],
geometry.attributes.position.array[rootIndex*3+1],
geometry.attributes.position.array[rootIndex*3+2]
);
if (geometry.attributes.color != undefined) {
out.colorArray.push(
geometry.attributes.color.array[rootIndex*3],
geometry.attributes.color.array[rootIndex*3+1],
geometry.attributes.color.array[rootIndex*3+2]
);
}
out.currentIndex += 1;
}
let child1Index = indexArray[triangleIndex*3+1];
if (out.indexMap[child1Index] === undefined) {
out.indexMap[child1Index] = out.currentIndex;
// add vertex position and color
out.positionArray.push(
geometry.attributes.position.array[child1Index*3],
geometry.attributes.position.array[child1Index*3+1],
geometry.attributes.position.array[child1Index*3+2]
);
if (geometry.attributes.color != undefined) {
out.colorArray.push(
geometry.attributes.color.array[child1Index*3],
geometry.attributes.color.array[child1Index*3+1],
geometry.attributes.color.array[child1Index*3+2]
);
}
out.currentIndex += 1;
}
let child2Index = indexArray[triangleIndex*3+2];
if (out.indexMap[child2Index] === undefined) {
out.indexMap[child2Index] = out.currentIndex;
// add vertex position and color
out.positionArray.push(
geometry.attributes.position.array[child2Index*3],
geometry.attributes.position.array[child2Index*3+1],
geometry.attributes.position.array[child2Index*3+2]
);
if (geometry.attributes.color != undefined) {
out.colorArray.push(
geometry.attributes.color.array[child2Index*3],
geometry.attributes.color.array[child2Index*3+1],
geometry.attributes.color.array[child2Index*3+2]
);
}
out.currentIndex += 1;
}
// Add indices:
out.indexArray.push(out.indexMap[rootIndex], out.indexMap[child1Index], out.indexMap[child2Index]);
}
const geoPositions = new Float32Array(out.positionArray);
const geoColors = new Float32Array(out.colorArray);
const geoIndices = new Uint32Array(out.indexArray);
const geo = new THREE.BufferGeometry();
geo.name = "G_" + i;
geo.index = new THREE.BufferAttribute(geoIndices, 1, false);
geo.attributes.position = new THREE.BufferAttribute(geoPositions, 3, false);
geo.attributes.color = new THREE.BufferAttribute(geoColors, 3, true);
geo.computeBoundingSphere();
geo.computeBoundingBox();
geometryArray.push(geo);
}
return geometryArray;
}

Resources