I have a few buttons in my app which load a new object onto the scene when clicked. Below is the code being used for loading new objects:
character = new THREE.UCSCharacter();
var ucspath = "skinnedModel.json";
manager = new THREE.LoadingManager();
manager.onProgress = function(url, itemsLoaded, itemsTotal) {
console.log(url, itemsLoaded, itemsTotal);
};
var onProgress = function (xhr){
if(xhr.lengthComputable){
var bar = 150;
percentComplete = xhr.loaded / xhr.total * 100;
bar = Math.floor( xhr.loaded / xhr.total * bar );
document.getElementById("bar").style.width = bar + "px";
}
};
var loader = new THREE.XHRLoader(manager);
loader.crossOrigin = true;
loader.load(ucspath, function (text) {
var config = JSON.parse(text);
character.loadParts(config);
avatar = character.root;
avatar.name = "female";
scene.add(avatar);
}, onProgress);
After clicking the button 10~12 times, the loading becomes increasingly slow and eventually the page crashes. I could not detect any unsual behavior using Chrome DevTools.
Is there something that needs to be done in order to load as many objects as desired without overloading the browser?
[Update] Heap Snapshot
Thanks in advance.
It seems that you're facing a performance issue caused by your model file.
Fortunately, the problem of a having a huge model replicated with different traits is very common in the game industry so I can give you an hint though it's not that simple :
In your 3D editor, merge all of your different geometries into one single model and give to each of your model parts an easy name like hair_01, head_02, beard_03, ...
Load this model in your Three.js app just once. This is your template model.
Now, when you want to create a new model with a different appearance :
Clone the template model ;
Hide the parts you don't want ;
Show the parts you want.
Here's an example of the idea :
var copy = template.clone();
copy.getObjectByName('head_01').visible = false;
copy.getObjectByName('head_02').visible = true;
scene.add(copy);
Cloning the model prevents the engine from duplicating all geometry data (*).
This is a picture of a single 3D model file from an existing game that contains every character geometries and switch them in runtime to give a humanoid various appearances.
*See THREE.Mesh.clone()
Related
I'm loading a 3D asset into a Away3D scene and I'd like to move the position of the bones in code.
The asset loading all goes well, I grab a pointer to the Mesh and Skeleton while loading:
private function onAssetComplete(evt:AssetEvent):void
{
if(evt.asset.assetType == AssetType.SKELETON){
_skeleton = evt.asset as Skeleton;
} else if (evt.asset.assetType == AssetType.MESH) {
_mesh = evt.asset as Mesh;
}
}
After the asset(s) have finished loading, I have a valid Skeleton and Mesh instance, the model is also visible in my scene. The next thing I tried is the following.
// create a matrix with the desired joint (bone) position
var pos:Matrix3D = new Matrix3D();
pos.position = new Vector3D(60, 0, 0);
pos.invert();
// get the joint I'd like to modifiy. The bone is named "left"
var joint:SkeletonJoint = _skeleton.jointFromName("left");
// assign joint position
joint.inverseBindPose = pos.rawData;
This code runs without error, but the new position isn't being applied to the visible geometry, eg. the position of the bone doesn't change at all.
Is there an additional step I'm missing here? Do I have to re-assign the skeleton to the Mesh somehow? Or do I have to explicitly tell the mesh that the bone positions have changed?
This might not be the best way to solve this, but here's what I figured out:
Away3D only applies joint transformations to the geometry when an animation is present. In order to apply your transforms, your geometry must have an animation or you'll have to create an animation in code. Here's how you do that (preferably in your LoaderEvent.RESOURCE_COMPLETE handler method:
// create a new pose for the skeleton
var rootPose:SkeletonPose = new SkeletonPose();
// add all the joints to the pose
// the _skeleton member is being assigned during the loading phase where you
// look for AssetType.SKELETON inside a AssetEvent.ASSET_COMPLETE listener
for each(var joint:SkeletonJoint in _skeleton.joints){
var m:Matrix3D = new Matrix3D(joint.inverseBindPose);
m.invert();
var p:JointPose = new JointPose();
p.translation = m.transformVector(p.translation);
p.orientation.fromMatrix(m);
rootPose.jointPoses.push(p);
}
// create idle animation clip by adding the root pose twice
var clip:SkeletonClipNode = new SkeletonClipNode();
clip.addFrame(rootPose, 1000);
clip.addFrame(rootPose, 1000);
clip.name = "idle";
// build animation set
var animSet:SkeletonAnimationSet = new SkeletonAnimationSet(3);
animSet.addAnimation(clip);
// setup animator with set and skeleton
var animator:SkeletonAnimator = new SkeletonAnimator(animSet, _skeleton);
// assign the newly created animator to your Mesh.
// This example assumes that you grabbed the pointer to _myMesh during the
// asset loading stage (by looking for AssetType.MESH)
_myMesh.animator = animator;
// run the animation
animator.play("idle");
// it's best to keep a member that points to your pose for
// further modification
_myPose = rootPose;
After that initialization step, you can modify your joint poses dynamically (you alter the position by modifying the translation property and the rotation by altering the orientation property). Example:
_myPose.jointPoses[2].translation.x = 100;
If you don't know the indices of your joints and rather address bones by name, this should work:
var jointIndex:int = _skeleton.jointIndexFromName("myBoneName");
_myPose.jointPoses[jointIndex].translation.y = 10;
If you use the name-lookup frequently (say every frame) and you have a lot of bones in your model, it's advisable to build a Dictionary where you can look up bone indices by name. The reason for this is that the implementation of jointIndexFromName performs a linear search through all joints which is wasteful if you do this multiple times.
Currently I have an intro screen to my flash file which has two objects.
A button which will load an external flash file using:
var myLoader:Loader = new Loader();
var url:URLRequest = new URLRequest("flashgame.swf");
The second thing is a Numeric Stepper, which will be from 1 to 10. If the user selects a number e.g. 3 then the game speed I have set in the flashgame.swf should be changed
Such as:
var gameSpeed:uint = 10 * numericStepper.value;
But I think my problem is coming into place because the stepper and gamespeed are from different files.
Anyone got any idea please?
I have also tried creating a stepper in the game file and used this code:
var gameLevel:NumericStepper = new NumericStepper();
gameLevel.maximum = 10;
gameLevel.minimum = 1;
addChild(gameLevel);
var gameSpeed:uint = 10 * gameLevel.value;
For some reason the stepper just flashes on the stage, no errors come up and the game doesn't work
When you execute you code, the stepper has no chance to wait for user input.
There is no time between theese two instructions.
addChild(gameLevel);
var gameSpeed:uint = 10 * gameLevel.value;
You should wait for user input in your NumericStepper, and then, on user event, set the game speed.
Edit: Yeah I know it's kinda sad to type out all this code (especially since some people wouldn't even be grateful enough to say thanks) but I think this question is important enough to justify the code as it may be helpful to others in future also.
Hi,
You were close. In your game file you could have put a var _setgameSpeed and then from Intro you could adjust it by flashgame._setgameSpeed = gameSpeed; It's a bit more complicated though since you also have to setup a reference to flashgame in the first place. Let me explain...
Ideally you want to put all your code in one place (an .as file would be best but...) if you would rather use timeline then you should create a new empty layer called "actions" and put all your code in the first frame of that.
Also change your button to a movieClip type and remove any code within it since everything will be controlled by the code in "actions" layer. In the example I have that movieclip on the stage with instance name of "btn_load_SWF"
Intro.swf (Parent SWF file)
var my_Game_Swf:MovieClip; //reference name when accessing "flashgame.swf"
var _SWF_is_loaded:Boolean = false; //initial value
var set_gameSpeed:int; //temp value holder for speed
var swf_loader:Loader = new Loader();
btn_load_SWF.buttonMode = true; //instance name of movieclip used as "load" button
btn_load_SWF.addEventListener(MouseEvent.CLICK, load_Game_SWF);
function load_Game_SWF (event:MouseEvent) : void
{
//set_gameSpeed = 10 * numericStepper.value;
set_gameSpeed = 100; //manual set cos I dont have the above numericStepper
if ( _SWF_is_loaded == true)
{
stage.removeChild(swf_loader);
swf_loader.load ( new URLRequest ("flashgame.swf") );
}
else
{ swf_loader.load ( new URLRequest ("flashgame.swf") ); }
swf_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, Game_SWF_ready);
}
function Game_SWF_ready (evt:Event) : void
{
swf_loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, Game_SWF_ready);
//Treat the Loader contents (flashgame.swf) as a MovieClip named "my_Game_Swf"
my_Game_Swf = swf_loader.content as MovieClip;
my_Game_Swf.gameSpeed = set_gameSpeed; //update gameSpeed variable in flashgame.swf
//also adjust SWF placement (.x and .y positions etc) here if necessary
stage.addChild(my_Game_Swf);
_SWF_is_loaded = true;
}
Now in you flashgame file make sure the there's also an actions layers and put in code like this below then compile it first before debugging the Intro/Parent file. When your Intro loads the flashgame.swf it should load an swf that already has the code below compiled.
flashgame.swf
var gameSpeed:int;
gameSpeed = 0; //initial value & will be changed by parent
addEventListener(Event.ADDED_TO_STAGE, onAdded_toStage);
function onAdded_toStage (e:Event):void
{
trace("trace gameSpeed is.." + String(gameSpeed)); //confirm speed in Debugger
//*** Example usage ***
var my_shape:Shape = new Shape();
my_shape.graphics.lineStyle(5, 0xFF0000, 5);
my_shape.graphics.moveTo(10, 50);
my_shape.graphics.lineTo(gameSpeed * 10, 50); //this line length is affected by gameSpeed as set in parent SWF
addChild(my_shape);
}
The key line in intro.swf is this: my_Game_Swf.gameSpeed = set_gameSpeed; as it updates a variable in flashgame.swf (referred as my_Game_Swf) with an amount that is taken from a variable in the Parent SWF.
This is just one way you can access information between two separate SWF files. Hope it helps out.
I have a class assignment where I have to create a photo gallery and one of the stipulations is that the images need to be loaded from an external source. The reference code from our assignment was:
var myRequest:URLRequest = new URLRequest("bw1.jpg");
myLoader.load(myRequest);
addChild(myLoader);
My specific needs requires me to have up to 2 copies of the images on the screen at once, one as a thumbnail and a second as a fullsized image. I want it so that when a user clicks a thumbnail a new instance of the picture is created scaled to fullsize with 0 alpha and the current selected picture decreases in alpha as the new pic increases in alpha. Once the old pic is no longer visible it is removed from the stage.
I am having trouble figuring out how to create a copy of the image in actionscript and also my for loop seems to not be executing properly as it only ever goes through one iteration even when I have no error messages.
Here is a link to my code via pastebin: http://pastebin.com/iadgKgsk but to save you from having to switch back and forth between I will also past it here
import flash.display.Bitmap;
//creates a loader for each picture to be loaded, I know I could have an empty array
that I add to but created a full array to test.
var loaders:Array = [new Loader(),new Loader(), new Loader(), new Loader(), new
Loader(), new Loader(), new Loader(), new Loader(), new Loader(), new Loader()];
//stores the url request for each image to be loaded
var Requests:Array =[new URLRequest("pic1.jpg"),new URLRequest("pic2.jpg"),new
URLRequest("pic3.jpg"),
new URLRequest("pic4.jpg"),new URLRequest("pic5.jpg"),new URLRequest("pic6.jpg"), new
URLRequest("pic7.jpg"),
new URLRequest("pic8.jpg"), new URLRequest("pic9.jpg"),new URLRequest("pic10.jpg")];
//creates 2 empty arrays one to store the thumbnail sized pics the other fullsized.
Ideally I want one Array holding the bitmap data and the other holding the thumbnail
instances since I only need 2
// fullsized images at a time I can just create a new one and erase the old one in a
single function.
var pics:Array = new Array();
var pics2:Array = new Array();
//defines an empty bitMap variable as a placeholder for which to copy from redefined in
every iteration of the loop
var test:Bitmap;
//loops through every loader
for (var i in loaders);
{
// loads the loader and url request and creates a picture
loaders[i].load(Requests[i]);
//waits for the pic to load
loaders[i].contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
//after loader is loaded create pic from the data
function loadComplete(evt:Event):void
{
var i = "Test";
trace(i);
test= evt.target.content as Bitmap;
pics2[i] = test;
pics[i] =new Bitmap(test.bitmapData);
pics2[i] =new Bitmap(test.bitmapData);
//creates an image on the stage at runtime to help debug
var pic1 = new Bitmap(test.bitmapData);
addChild(pics[i])
pic1.scaleX = 0.138427734375;
pic1.scaleY = 0.1384114583333333;
pic1.x = 204;
pic1.y = 20.6;
pic1.alpha = .25;
var pic2:Bitmap = new Bitmap(test.bitmapData);
pic2.x =100;
pic2.y =100;
pic2.scaleX =.33;
pic2.scaleY=.33;
addChild(pic2);
loaders[i].contentLoaderInfo.removeEventListener(Event.COMPLETE,
loadComplete)
}
}
Your for loop is setup in a way that it would attempt to iterate for each property of the loaders instance which isn't what you want. You may want to investigate other AS3 bulk loader options too there are already some scripts out there to help take care of this.
Your loop should look like
for(var i=0; i<loaders.length; i++)
{
var curLoader = loaders[i].load(Requests[i]);
You can also see an example I put together that loads a bunch of images flipbook style animation (images generated with blender):
http://www.shaunhusain.com/DrawTextRandomly/
http://www.shaunhusain.com/DrawTextRandomly/srcview/
look in the utils.imageLoader package, I made two classes there that deal with loading the images, since they were output from Blender with sequential file names I just used some loops to load the images based on a numeric counter, in your case you could switch it to use a source array of images to load.
In the BatchImageLoader class I have a variable for how many loaders to allow it to make, I wanted to see the difference in performance in terms of memory vs load time (will depend on runtime environment too but I wanted to get a general idea, ended with leaving it at using 90 loaders at most).
In flex builder 4.5 i'm working on a project like cacoo.
I want to save diagrams(display object,ui components,text) before close the application into somewhere than I would be able to access after the application open again.
more clear:-If user edit some uml diagram on this project and save it for edit later and close application.after some days he/she want to edit previously saved diagram.
now how i'm save this diagram for future edit.
If save/open dialog will work for you, you can yse FileReference API. Before doing this, you have to implement serialization/deserialization of your state into/from String/ByteArray/XML object.
private var fileReference:FileReference;
// due to security restrictions, this method must be called from an
// event handler that responds to a user event (mouse click or key
// press), otherwise it will fail.
private function saveState(serializedState:*, fileName:String):void {
fileReference = new FileReference();
fileReference.addEventListener(Event.COMPLETE, onSaved);
fileReference.addEventListener(IOErrorEvent.IO_ERROR, onSavingError);
try {
fileReference.save(serializedState, fileName); // will open save dialog
} catch (e:Error) {
trace("error saving data: " + e.toString());
freeListeners();
}
}
private function onSaved(e:Event):void {
trace("saved!");
freeListeners();
}
private function onSavingError(e:ErrorEvent):void {
trace("error saving data: " + e.toString());
freeListeners();
}
private function freeListeners():void {
fileReference.removeEventListener(Event.COMPLETE, onSaved);
fileReference.removeEventListener(IOErrorEvent.IO_ERROR, onSavingError);
}
Similarly with restoring the state (use FileReference.browse(), then FileReference.load()).
If you need to save/restore app state without any dialogs, then you should probably use AIR (or SharedObject, as Raja Jaganathan suggested). But it seems to be not the case, as you want the user to be able to re-open the diagram in another system. To achieve this, you should allow the user to save his work to the appropriate place, so later he can move it to another machine/system and re-open it with your application.
Another alternative is to store everything on the server and provide the user with a list of saved files (like Cacoo does). If you go this way, you'll have to implement the corresponding server-side API. It may be REST API or smth like RTMP server. In the case of REST API, use FileReference.upload() to upload the data to your server, and URLLoader.load() to obtain it back.
You can store your diagram state through SharedObject for better you create one class which hold all of your state of Diagram so that later you can use
SharedObject using http://livedocs.adobe.com/flex/3/html/help.html?content=lsos_5.html
you can use registerClassAlias for custom class stored in sharedobject.
myClassInstance = new MyClass();
myClassInstance.x = 100;
myClassInstance.y = 100;
myClassInstance.text = "diagrams";
registerClassAlias("com.path.to.MyClass", MyClass);
myStuff = SharedObject.getLocal("myAppStuff");
myStuff.data.whatINamedIt = myClassInstance;
myStuff.flush();
now when get it back out... you can say:
myStuff = SharedObject.getLocal("myAppStuff");
var mySavedClass:MyClass = myStuff.data.whatINamedIt as MyClass;
Read mySavedClass instance value then inject to your diagram model when open again.
To implement application close event
http://www.flexer.info/2007/10/25/fabridge-warn-on-flex-application-exit/
Sprite or MovieClip other DisplayObject objects can not be direct serialized. So you should stored objects information (origin x,y, width, height, color, child info...). using a ByteArray or Array or Dictionary ... and that save to ShareObjects. later roll back from ShareObject and re-create Original Object. MovieClip or Sprite appropriate purpose is container.
Here is my test code.
1. create a Movieclip. purpose is container.
2. draw a rectangle using a graphics. And set the coordinates.
var drawWidth:Number = 500;
var drawHeight:Number = 300;
var rect:MovieClip = new MyRect();
rect.graphics.beginFill(0xffffff*Math.random(),1);
rect.graphics.drawRect(0,0,drawWidth,drawHeight);
rect.graphics.endFill();
rect.x= 300;
rect.y= 100;
3. Stores the information in the array.
var myRectInformation:Array = new Array();
myRectInformation.push(rect.x);
myRectInformation.push(rect.y);
myRectInformation.push(drawWidth);
myRectInformation.push(drawHeight);
var bmd:BitmapData = new BitmapData(rect.width, rect.height,true,0);
bmd.draw(rect);
//is byteArray.
myRectInformation.push(bmd.getPixels(new Rectangle(0,0,bmd.width,bmd.height)));
4. save to SharedObjects, array.
var mySaveData:SharedObject = SharedObject.getLocal("myStorage")
mySaveData.data.myRectInformation = myRectInformation;
mySaveData.flush();
5. this is load from SharedObject data stored. and recreate Objects.
var rect:MovieClip = new MyRect();
var loadBmd:BitmapData = new BitmapData(mySaveData.data.myRectInformation[2], mySaveData.data.myRectInformation[3], true, 1);
loadBmd.setPixels(new Rectangle(0,0,loadBmd.width,loadBmd.height), mySaveData.data.myRectInformation[4]);
var bmp:Bitmap = new Bitmap(loadBmd);
rect.addChild(bmp);
rect.x = mySaveData.data.myRectInformation[0];
rect.y = mySaveData.data.myRectInformation[1];
addChild(rect);
My goal was to have an external text file config for a client. I didnt want to go through a crazy xml thing, I just wanted it to be simple to change.
I started with a urlLoader, and was able to dynamically generate an object no problem. This is the function which parses and sets the properties of the object.
function onLoaded(e:Event):void//initializes the config
{
var myString = String(e.target.data);
//trace(e.target.data);
//trace(myString);
var propsArray:Array = myString.split("\n");
for (var i = 0; i < propsArray.length; i++){
var thisLine:Array = propsArray[i].split("=");
var thisPropName:String = thisLine[0];
thisPropName = thisPropName.replace(rex,'');
var thisPropValue:String = thisLine[1];
thisPropValue = thisPropValue.replace(rex,'');
trace("thePropName is: " + thisPropName);
trace("thePropValue is: " + thisPropValue);
config[thisPropName] = thisPropValue;
}
}
The text file would just look something like:
gateway = "http://thePathto/theFile.php
toast = sonofabitch
timer = 5000
xSpeed = 5.0
That way, I could just put a little bit of as3 code in, type what things I wanted configured, then all I would have to do was type config.timer and
var myTimer:Timer = new Timer(Number(config.timer));
I think the problem is load order and scope. The config.timer is not created yet, so the timer is unable to access the value of the config.timer.
I'd look at using XML in future projects of this nature, however to answer your question:
I think the problem is load order and scope. The config.timer is not created yet, so the timer is unable to access the value of the config.timer.
Correct, you will need to initialize your Timer within the onLoaded() method, as the data will be received asynchronously and is not available until this happens.
ok not long ago i had created a download manager that uses this exact concept.
The link below will take you straight to the website where you can download the full swf including my source files. also this website is a good place for resources
http://ffiles.com/flash/web_applications_and_data/dynamic_download_manager_3529.html
Below is my loader:
addEventListener(Event.ENTER_FRAME, update);
var myLoader:URLLoader = new URLLoader();
myLoader.dataFormat = URLLoaderDataFormat.VARIABLES;
myLoader.load(new URLRequest("settings.txt"));
myLoader.addEventListener(Event.COMPLETE, onDataLoad);
function onDataLoad(evt:Event)
{
box1.text = evt.target.data.Id_1;
box2.text = evt.target.data.Id_2;
box3.text = evt.target.data.Id_3;
box4.text = evt.target.data.Id_4;
box5.text = evt.target.data.Id_5;
}
Add some dynamic text boxes to stage and name them "box1, box2 ect..."
Now creat your text file:
Id_1=this is what ever you want
&Id_2=this is what ever you want
&Id_3=this is what ever you want
&Id_4=this is what ever you want
&Id_5=this is what ever you want
Hope this helps.