Creating objects on the fly

I’m developing an Application that helps players of Magic the Gathering and I’ve come to UI design time - CGE looks like a good option.

After weeks of messing with Blender (never used it before) I’ve hacked together a test obj of a MTG card that looks nice + realistic (new member - no upload so if you want to see what I’m banging on about the obj is at https://peardox.com/script/mtg.zip)

Anyway one obj can define any MTG card. All that changes is the image in the front of the card. In card.mtl replacing front.png with another card image is all that’s required.

Usually I’d use something like Scene.LoadFromStream(…) but there ain’t one

Basically the only thing that’s required is changing a filename to create any of the 25k+ possible cards.

I’d like to load in and composite the image used from a JPEG and a mask (PNGs are huge).

I presume there’s some way to have a base model you can switch the texture on - just can’t work out what to do

Hi!

After weeks of messing with Blender (never used it before) I’ve hacked together a test obj of a MTG card that looks nice + realistic (new member - no upload so if you want to see what I’m banging on about the obj is at https://peardox.com/script/mtg.zip)

Some notes about the OBJ model:

Anyway one obj can define any MTG card. All that changes is the image in the front of the card. In card.mtl replacing front.png with another card image is all that’s required.

The optimal way to do this is to load your model to an “X3D node graph” (instance of TX3DRootNode), replace the texture, and then create TCastleScene from this node graph.

So instead of loading like this:

function LoadCard: TCastleScene;
begin
  Result := TCastleScene.Create(nil);
  Result.Load('castle-data:/card.obj');
end;

you would instead do this:

function LoadCard(const TextureUrl: String): TCastleScene;
var
  CardNode: TX3DRootNode;
  TextureNode: TImageTextureNode;
begin
  Result := TCastleScene.Create(nil);
  
  CardNode := LoadNode('castle-data:/card.obj');
  TextureNode := CardNode.FindNode(TImageTextureNode, false) as TImageTextureNode;
  TextureNode.SetUrl([TextureUrl]);
  
  Result.Load(CardNode, true);
end;

This is a general way to process a model in CGE. So you process TX3DRootNode before passing it to TCastleScene.Load .

There are various ways to customize it:

  • Instead of CardNode.FindNode(TImageTextureNode, false) you could look for a node in different ways or enumerate all nodes of certain types. See the API references of TX3DNode and various methods like FindNode, TryFindNode, EnumerateNodes etc.

  • Instead of loading CardNode from file each time (in each LoadCard invocation), you can load it once (e.g. make CardNode a global variable and call CardNode := LoadNode('castle-data:/card.obj') once, at the application initialization). Then in LoadCard you would merely copy an existing graph by DeepCopy, like this:

function LoadCard(const TextureUrl: String): TCastleScene;
var
  CardNodeCopy: TX3DRootNode;
  TextureNode: TImageTextureNode;
begin
  Result := TCastleScene.Create(nil);
  
  CardNodeCopy := CardNode.DeepCopy as TX3DRootNode;
  TextureNode := CardNodeCopy.FindNode(TImageTextureNode, false) as TImageTextureNode;
  TextureNode.SetUrl([TextureUrl]);
  
  Result.Load(CardNodeCopy, true);
end;

// call like 
// MyScene := LoadCard('castle-data:/textures/some_different_texture.jpg');

This way the file is loaded only once, and your code only creates multiple copies of the graph, each copy with a different texture URL.

1 Like

P.S. I forgot to mention important hints: How to investigate this “X3D node graph” that I talk about ? :slight_smile:

  • It’s easiest to open the OBJ file in view3dscene (you have it in engine bin/ subdirectory, if you downloaded CGE binary release, or you can get it from https://michalis.ii.uni.wroc.pl/view3dscene-snapshots/ ). In view3dscene, save it to X3D using “File->Save as X3D (classic…)” menu item. Then open the resulting xxx.x3dv file in any text editor. You can search there e.g. for ImageTexture, this is the texture node (available in Pascal as TImageTextureNode instance). All the other nodes can be processed in the same way in CGE, you can add/remove nodes, you can even do it at runtime (after loading the scene, although some changes are more expensive then).

  • https://castle-engine.io/vrml_x3d.php contains documentation of these nodes. Actually the X3D specification is the documentation of most of these nodes, and our https://castle-engine.io/vrml_x3d.php (and subpages) links to it. The texture nodes are on this subpage: https://castle-engine.io/x3d_implementation_texturing.php .

Implemented as you suggested with some positioning added to LoadCard then added 12 of them to the ViewPort.

Looks better running than the screengrab suggests (grab’s slightly blurred for some reason)

Given the following image is there a simple way to click on one of the Cards and detect which one it was?

Next steps - add a background + animate some cards

Completely unimportant info - That selection of cards is called Red Deck Wins (well one variant of it)

Sure. When each card is a separate TCastleScene, you can just look at Viewport.MouseRayHit.First.Item in a press event (like TCastleWindowBase.OnPress). This is an instance of TCastleTransform (ancestor of TCastleScene) under the mouse. Example is in https://github.com/castle-engine/castle-engine/blob/master/examples/3d_rendering_processing/detect_scene_clicks.lpr .

Well that was indeed, as requested, nice and simple.

Touch is also detected on my laptop. I’ll have to try out iOS + Android at some point soon (fpcupdeluxe is being awkward on Windows, yet to try Mac)…