Browse Source

Rendering a model brush in Sandbox edit mode.

David Piuva 5 years ago
parent
commit
c3f3cdf0d5

+ 53 - 42
Source/SDK/sandbox/sandbox.cpp

@@ -146,7 +146,9 @@ static int random(const int minimum, const int maximum) {
 }
 
 // Variables
-static Sprite brush(0, dir0, IVector3D(), true);
+static int brushHeight = 0; // In mini-tile units
+static SpriteInstance spriteBrush(0, dir0, IVector3D(), true);
+static ModelInstance modelBrush(0, Transform3D());
 static const int brushStep = ortho_miniUnitsPerTile / 32;
 static int buttonPressed[4] = {0, 0, 0, 0};
 static IVector2D cameraMovement;
@@ -176,8 +178,15 @@ void updateOverlay() {
 }
 
 void loadSprite(const ReadableString& name) {
-	sprite_loadTypeFromFile(imagePath, name);
+	spriteWorld_loadSpriteTypeFromFile(imagePath, name);
 	component_call(spriteList, U"PushElement", name);
+	component_setProperty_integer(spriteList, U"SelectedIndex", 0);
+}
+
+void loadModel(const ReadableString& name, const ReadableString& visibleName, const ReadableString& shadowName) {
+	spriteWorld_loadModelTypeFromFile(modelPath, visibleName, shadowName);
+	component_call(modelList, U"PushElement", name);
+	component_setProperty_integer(modelList, U"SelectedIndex", 0);
 }
 
 void sandbox_main() {
@@ -253,9 +262,9 @@ void sandbox_main() {
 			} else if (key == DsrKey_S) {
 				buttonPressed[3] = 1;
 			} else if (key == DsrKey_LeftArrow) {
-				brush.direction = correctDirection(brush.direction + dir270);
+				spriteBrush.direction = correctDirection(spriteBrush.direction + dir270);
 			} else if (key == DsrKey_RightArrow) {
-				brush.direction = correctDirection(brush.direction + dir90);
+				spriteBrush.direction = correctDirection(spriteBrush.direction + dir90);
 			}
 		} else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
 			if (key == DsrKey_A) {
@@ -277,8 +286,8 @@ void sandbox_main() {
 		if (event.key == MouseKeyEnum::Left) {
 			if (overlayMode == OverlayMode_Tools) {
 				if (tool == Tool_PlaceSprite) {
-					// Place a new visual instance using the brush
-					spriteWorld_addBackgroundSprite(world, brush);
+					// Place a new visual instance using the sprite brush
+					spriteWorld_addBackgroundSprite(world, spriteBrush);
 				} else if (tool == Tool_PlaceModel) {
 					// TODO: Implement a way to place a background model with 3-dimensional location, 3-axis rotation and uniform scaling
 				}
@@ -294,9 +303,9 @@ void sandbox_main() {
 	});
 	component_setMouseScrollEvent(mainPanel, [](const MouseEvent& event) {
 		if (event.key == MouseKeyEnum::ScrollUp) {
-			brush.location.y += brushStep;
+			brushHeight += brushStep;
 		} else if (event.key == MouseKeyEnum::ScrollDown) {
-			brush.location.y -= brushStep;
+			brushHeight -= brushStep;
 		}
 	});
 
@@ -313,17 +322,17 @@ void sandbox_main() {
 	});
 	spriteList = window_findComponentByName(window, U"spriteList");
 	component_setSelectEvent(spriteList, [](int64_t index) {
-		brush.typeIndex = index;
+		spriteBrush.typeIndex = index;
 	});
 	modelList = window_findComponentByName(window, U"modelList");
 	component_setSelectEvent(modelList, [](int64_t index) {
-		// TODO: Implement model selection from the list
+		modelBrush.typeIndex = index;
 	});
 	component_setPressedEvent(window_findComponentByName(window, U"leftButton"), []() {
-		brush.direction = correctDirection(brush.direction + dir270);
+		spriteBrush.direction = correctDirection(spriteBrush.direction + dir270);
 	});
 	component_setPressedEvent(window_findComponentByName(window, U"rightButton"), []() {
-		brush.direction = correctDirection(brush.direction + dir90);
+		spriteBrush.direction = correctDirection(spriteBrush.direction + dir90);
 	});
 	updateOverlay();
 
@@ -336,26 +345,24 @@ void sandbox_main() {
 	loadSprite(U"Character_Mage");
 
 	// Load models
-	DenseModel barrelVisible = DenseModel_create(importer_loadModel(modelPath + U"Barrel_LowDetail.ply", true, Transform3D()));
-	Model barrelShadow = importer_loadModel(modelPath + U"Barrel_Shadow.ply", true, Transform3D());
-	//DenseModel barrelVisible = DenseModel_create(importer_loadModel(modelPath + U"Character_Mage.ply", true, Transform3D()));
-	//Model barrelShadow = importer_loadModel(modelPath + U"Character_Mage_Shadow.ply", true, Transform3D());
+	loadModel(U"Barrel", U"Barrel_LowDetail.ply", U"Barrel_Shadow.ply");
+	loadModel(U"Mage", U"Character_Mage.ply", U"Character_Mage_Shadow.ply");
 
 	// Create passive sprites
 	for (int z = -300; z < 300; z++) {
 		for (int x = -300; x < 300; x++) {
 			// The bottom floor does not have to throw shadows
-			spriteWorld_addBackgroundSprite(world, Sprite(random(0, 1), random(0, 3) * dir90, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), false));
+			spriteWorld_addBackgroundSprite(world, SpriteInstance(random(0, 1), random(0, 3) * dir90, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), false));
 		}
 	}
 	for (int z = -300; z < 300; z++) {
 		for (int x = -300; x < 300; x++) {
 			if (random(1, 4) == 1) {
 				// Obstacles should cast shadows when possible
-				spriteWorld_addBackgroundSprite(world, Sprite(random(2, 4), random(0, 3) * dir90, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), true));
+				spriteWorld_addBackgroundSprite(world, SpriteInstance(random(2, 4), random(0, 3) * dir90, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), true));
 			} else if (random(1, 20) == 1) {
 				// Characters are just static geometry for testing
-				spriteWorld_addBackgroundSprite(world, Sprite(5, random(0, 7) * dir45, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), true));
+				spriteWorld_addBackgroundSprite(world, SpriteInstance(5, random(0, 7) * dir45, IVector3D(x * ortho_miniUnitsPerTile, 0, z * ortho_miniUnitsPerTile), true));
 			}
 		}
 	}
@@ -372,8 +379,6 @@ void sandbox_main() {
 	double maxFrameTime = 0.0, lastMaxFrameTime = 0.0; // Peak per second
 
 	while(running) {
-		double timer = time_getSeconds();
-
 		// Always render the image when profiling or moving the camera
 		updateImage = overlayMode != OverlayMode_Tools || cameraMovement.x != 0 || cameraMovement.y != 0;
 
@@ -397,7 +402,7 @@ void sandbox_main() {
 			// Move the camera
 			int cameraSteps = (int)(cameraSpeed * msTicks);
 			// TODO: Find a way to move the camera using exact pixel offsets so that the camera's 3D location is only generating the 2D offset when rotating.
-			//       Can the brush be guaranteed to come back to the mouse location after adding and subtracting the same 2D camera offset?
+			//       Can the sprite brush be guaranteed to come back to the mouse location after adding and subtracting the same 2D camera offset?
 			//         A new integer coordinate system along the ground might move half a pixel vertically and a full pixel sideways in the diagonal view.
 			//       Otherwise the approximation defeats the whole purpose of using whole integers in msTicks.
 			spriteWorld_moveCameraInPixels(world, cameraMovement * cameraSteps);
@@ -407,10 +412,15 @@ void sandbox_main() {
 
 			// Place the brush
 			IVector3D mouseWorldPos = spriteWorld_findGroundAtPixel(world, colorBuffer, mousePos);
-			brush.location.x = mouseWorldPos.x;
-			brush.location.z = mouseWorldPos.z;
+			modelBrush.location = Transform3D(FVector3D(
+			  mouseWorldPos.x * ortho_tilesPerMiniUnit,
+			  brushHeight * ortho_tilesPerMiniUnit,
+			  mouseWorldPos.z * ortho_tilesPerMiniUnit),
+			  FMatrix3x3::makeAxisSystem(FVector3D(1.0f, 0.0f, 0.0f), FVector3D(0.0f, 1.0f, 0.0f)) // TODO: An integer based rotation system for the brush
+			);
+			spriteBrush.location = IVector3D(mouseWorldPos.x, brushHeight, mouseWorldPos.z);
 			if (tileAlign) {
-				brush.location = ortho_roundToTile(brush.location);
+				spriteBrush.location = ortho_roundToTile(spriteBrush.location);
 			}
 
 			// Illuminate the world using soft light from the sky
@@ -421,32 +431,33 @@ void sandbox_main() {
 			// Create a temporary point light over the brush
 			//   Temporary light sources are easier to use for dynamic light because they don't need any handle
 			if (mouseLights == 1) {
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(0.0f, 0.5f, 0.0f), 4.0f, 4.0f, ColorRgbI32(128, 255, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(0.0f, 0.5f, 0.0f), 4.0f, 4.0f, ColorRgbI32(128, 255, 128), true);
 			} else if (mouseLights == 2) {
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 2.0f, ColorRgbI32(255, 128, 128), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 2.0f, ColorRgbI32(128, 255, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 2.0f, ColorRgbI32(255, 128, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 2.0f, ColorRgbI32(128, 255, 128), true);
 			} else if (mouseLights == 3) {
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 1.333f, ColorRgbI32(255, 128, 128), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(1.0f, 0.51f, 2.0f), 4.0f, 1.333f, ColorRgbI32(128, 255, 128), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 1.333f, ColorRgbI32(128, 128, 255), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 1.333f, ColorRgbI32(255, 128, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(1.0f, 0.51f, 2.0f), 4.0f, 1.333f, ColorRgbI32(128, 255, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 1.333f, ColorRgbI32(128, 128, 255), true);
 			} else if (mouseLights == 4) {
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 1.0f, ColorRgbI32(255, 128, 128), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(1.0f, 0.51f, 2.0f), 4.0f, 1.0f, ColorRgbI32(128, 255, 128), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 1.0f, ColorRgbI32(128, 128, 255), true);
-				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(brush.location) + FVector3D(-1.0f, 0.53f, -2.0f), 4.0f, 1.0f, ColorRgbI32(255, 255, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(-2.0f, 0.5f, 1.0f), 4.0f, 1.0f, ColorRgbI32(255, 128, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(1.0f, 0.51f, 2.0f), 4.0f, 1.0f, ColorRgbI32(128, 255, 128), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(2.0f, 0.52f, -1.0f), 4.0f, 1.0f, ColorRgbI32(128, 128, 255), true);
+				spriteWorld_createTemporary_pointLight(world, ortho_miniToFloatingTile(spriteBrush.location) + FVector3D(-1.0f, 0.53f, -2.0f), 4.0f, 1.0f, ColorRgbI32(255, 255, 128), true);
 			}
 
-			// Show the brush
+			// Show the sprite brush
 			if (overlayMode == OverlayMode_Tools) {
-				if (tool == Tool_PlaceSprite) {
-					spriteWorld_addTemporarySprite(world, brush);
-				} else if (tool == Tool_PlaceModel) {
-					// TODO: Implement preview of freely rotated background model brush
-					//spriteWorld_addTemporaryModel(world, ModelInstance(?, ?, ?));
+				if (tool == Tool_PlaceSprite && spriteWorld_getSpriteTypeCount() > 0) {
+					spriteWorld_addTemporarySprite(world, spriteBrush);
+				} else if (tool == Tool_PlaceModel && spriteWorld_getModelTypeCount() > 0) {
+					spriteWorld_addTemporaryModel(world, modelBrush);
 				}
 			}
 
 			// Test freely rotated models
+			/*
+			double timer = time_getSeconds();
 			if (overlayMode != OverlayMode_Tools) {
 				for(int t = 0; t < testModelCount; t++) {
 					float scale = 1.0f;
@@ -456,7 +467,7 @@ void sandbox_main() {
 					);
 					spriteWorld_addTemporaryModel(world, ModelInstance(barrelVisible, barrelShadow, testLocation));
 				}
-			}
+			}*/
 
 			// Draw the world
 			spriteWorld_draw(world, colorBuffer);

+ 60 - 38
Source/SDK/sandbox/sprite/spriteAPI.cpp

@@ -212,17 +212,48 @@ public:
 	}
 };
 
+struct ModelType {
+public:
+	DenseModel visibleModel;
+	Model shadowModel;
+public:
+	// folderPath should end with a path separator
+	ModelType(const String& folderPath, const String& visibleModelName, const String& shadowModelName) {
+		this->visibleModel = DenseModel_create(importer_loadModel(folderPath + visibleModelName, true, Transform3D()));
+		this->shadowModel = importer_loadModel(folderPath + shadowModelName, true, Transform3D());
+	}
+	ModelType(const DenseModel& visibleModel, const Model& shadowModel)
+	: visibleModel(visibleModel), shadowModel(shadowModel) {}
+};
+
 // Global list of all sprite types ever loaded
-List<SpriteType> types;
+List<SpriteType> spriteTypes;
+int spriteWorld_loadSpriteTypeFromFile(const String& folderPath, const String& spriteName) {
+	spriteTypes.pushConstruct(folderPath, spriteName);
+	return spriteTypes.length() - 1;
+}
+int spriteWorld_getSpriteTypeCount() {
+	return spriteTypes.length();
+}
+
+// Global list of all model types ever loaded
+List<ModelType> modelTypes;
+int spriteWorld_loadModelTypeFromFile(const String& folderPath, const String& visibleModelName, const String& shadowModelName) {
+	modelTypes.pushConstruct(folderPath, visibleModelName, shadowModelName);
+	return modelTypes.length() - 1;
+}
+int spriteWorld_getModelTypeCount() {
+	return modelTypes.length();
+}
 
-static int getSpriteFrameIndex(const Sprite& sprite, OrthoView view) {
-	return types[sprite.typeIndex].getFrameIndex(view.worldDirection + sprite.direction);
+static int getSpriteFrameIndex(const SpriteInstance& sprite, OrthoView view) {
+	return spriteTypes[sprite.typeIndex].getFrameIndex(view.worldDirection + sprite.direction);
 }
 
 // Returns a 2D bounding box of affected target pixels
-static IRect drawSprite(const Sprite& sprite, const OrthoView& ortho, const IVector2D& worldCenter, ImageF32 targetHeight, ImageRgbaU8 targetColor, ImageRgbaU8 targetNormal) {
+static IRect drawSprite(const SpriteInstance& sprite, const OrthoView& ortho, const IVector2D& worldCenter, ImageF32 targetHeight, ImageRgbaU8 targetColor, ImageRgbaU8 targetNormal) {
 	int frameIndex = getSpriteFrameIndex(sprite, ortho);
-	const SpriteFrame* frame = &types[sprite.typeIndex].frames[frameIndex];
+	const SpriteFrame* frame = &spriteTypes[sprite.typeIndex].frames[frameIndex];
 	IVector2D screenSpace = ortho.miniTilePositionToScreenPixel(sprite.location, worldCenter) - frame->centerPoint;
 	float heightOffset = sprite.location.y * ortho_tilesPerMiniUnit;
 	draw_higher(targetHeight, frame->heightImage, targetColor, frame->colorImage, targetNormal, frame->normalImage, screenSpace.x, screenSpace.y, heightOffset);
@@ -230,7 +261,7 @@ static IRect drawSprite(const Sprite& sprite, const OrthoView& ortho, const IVec
 }
 
 static IRect drawModel(const ModelInstance& instance, const OrthoView& ortho, const IVector2D& worldCenter, ImageF32 targetHeight, ImageRgbaU8 targetColor, ImageRgbaU8 targetNormal) {
-	return renderDenseModel<false>(instance.visibleModel, ortho, targetHeight, targetColor, targetNormal, FVector2D(worldCenter.x, worldCenter.y), instance.location);
+	return renderDenseModel<false>(modelTypes[instance.typeIndex].visibleModel, ortho, targetHeight, targetColor, targetNormal, FVector2D(worldCenter.x, worldCenter.y), instance.location);
 }
 
 // The camera transform for each direction
@@ -281,11 +312,11 @@ public:
 	PointLight(FVector3D position, float radius, float intensity, ColorRgbI32 color, bool shadowCasting)
 	: position(position), radius(radius), intensity(intensity), color(color), shadowCasting(shadowCasting) {}
 public:
-	void renderModelShadow(CubeMapF32& shadowTarget, const ModelInstance& instance, const FMatrix3x3& normalToWorld) const {
-		Model model = instance.shadowModel;
+	void renderModelShadow(CubeMapF32& shadowTarget, const ModelInstance& modelInstance, const FMatrix3x3& normalToWorld) const {
+		Model model = modelTypes[modelInstance.typeIndex].shadowModel;
 		if (model_exists(model)) {
 			// Place the model relative to the light source's position, to make rendering in light-space easier
-			Transform3D modelToWorldTransform = instance.location;
+			Transform3D modelToWorldTransform = modelInstance.location;
 			modelToWorldTransform.position = modelToWorldTransform.position - this->position;
 			for (int s = 0; s < 6; s++) {
 				Camera camera = Camera::createPerspective(Transform3D(FVector3D(), ShadowCubeMapSides[s] * normalToWorld), shadowTarget.resolution, shadowTarget.resolution);
@@ -293,12 +324,12 @@ public:
 			}
 		}
 	}
-	void renderSpriteShadow(CubeMapF32& shadowTarget, const Sprite& sprite, const FMatrix3x3& normalToWorld) const {
-		if (sprite.shadowCasting) {
-			Model model = types[sprite.typeIndex].shadowModel;
+	void renderSpriteShadow(CubeMapF32& shadowTarget, const SpriteInstance& spriteInstance, const FMatrix3x3& normalToWorld) const {
+		if (spriteInstance.shadowCasting) {
+			Model model = spriteTypes[spriteInstance.typeIndex].shadowModel;
 			if (model_exists(model)) {
 				// Place the model relative to the light source's position, to make rendering in light-space easier
-				Transform3D modelToWorldTransform = Transform3D(ortho_miniToFloatingTile(sprite.location) - this->position, spriteDirections[sprite.direction]);
+				Transform3D modelToWorldTransform = Transform3D(ortho_miniToFloatingTile(spriteInstance.location) - this->position, spriteDirections[spriteInstance.direction]);
 				for (int s = 0; s < 6; s++) {
 					Camera camera = Camera::createPerspective(Transform3D(FVector3D(), ShadowCubeMapSides[s] * normalToWorld), shadowTarget.resolution, shadowTarget.resolution);
 					model_renderDepth(model, modelToWorldTransform, shadowTarget.cubeMapViews[s], camera);
@@ -306,11 +337,11 @@ public:
 			}
 		}
 	}
-	void renderSpriteShadows(CubeMapF32& shadowTarget, Octree<Sprite>& sprites, const FMatrix3x3& normalToWorld) const {
+	void renderSpriteShadows(CubeMapF32& shadowTarget, Octree<SpriteInstance>& sprites, const FMatrix3x3& normalToWorld) const {
 		IVector3D center = ortho_floatingTileToMini(this->position);
 		IVector3D minBound = center - ortho_floatingTileToMini(radius);
 		IVector3D maxBound = center + ortho_floatingTileToMini(radius);
-		sprites.map(minBound, maxBound, [this, shadowTarget, normalToWorld](Sprite& sprite, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound) mutable {
+		sprites.map(minBound, maxBound, [this, shadowTarget, normalToWorld](SpriteInstance& sprite, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound) mutable {
 			this->renderSpriteShadow(shadowTarget, sprite, normalToWorld);
 		});
 	}
@@ -368,7 +399,7 @@ private:
 		);
 	}
 	// Pre-condition: diffuseBuffer must be cleared unless sprites cover the whole block
-	void draw(Octree<Sprite>& sprites, const OrthoView& ortho) {
+	void draw(Octree<SpriteInstance>& sprites, const OrthoView& ortho) {
 		image_fill(this->normalBuffer, ColorRgbaI32(128));
 		image_fill(this->heightBuffer, -std::numeric_limits<float>::max());
 		sprites.map(
@@ -419,19 +450,19 @@ private:
 			}
 			return true;
 		},
-		[this, ortho](Sprite& sprite, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound){
+		[this, ortho](SpriteInstance& sprite, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound){
 			drawSprite(sprite, ortho, -this->worldRegion.upperLeft(), this->heightBuffer, this->diffuseBuffer, this->normalBuffer);
 		});
 	}
 public:
-	BackgroundBlock(Octree<Sprite>& sprites, const IRect& worldRegion, const OrthoView& ortho)
+	BackgroundBlock(Octree<SpriteInstance>& sprites, const IRect& worldRegion, const OrthoView& ortho)
 	: worldRegion(worldRegion), cameraId(ortho.id), state(BlockState::Ready),
 	  diffuseBuffer(image_create_RgbaU8(blockSize, blockSize)),
 	  normalBuffer(image_create_RgbaU8(blockSize, blockSize)),
 	  heightBuffer(image_create_F32(blockSize, blockSize)) {
 		this->draw(sprites, ortho);
 	}
-	void update(Octree<Sprite>& sprites, const IRect& worldRegion, const OrthoView& ortho) {
+	void update(Octree<SpriteInstance>& sprites, const IRect& worldRegion, const OrthoView& ortho) {
 		this->worldRegion = worldRegion;
 		this->cameraId = ortho.id;
 		image_fill(this->diffuseBuffer, ColorRgbaI32(0));
@@ -461,10 +492,10 @@ public:
 	// World
 	OrthoSystem ortho;
 	// Sprites that rarely change and can be stored in a background image. (no animations allowed)
-	// TODO: Don't store the position twice, by keeping it separate from the Sprite struct.
-	Octree<Sprite> passiveSprites;
+	// TODO: Don't store the position twice, by keeping it separate from the SpriteInstance struct.
+	Octree<SpriteInstance> passiveSprites;
 	// Temporary things are deleted when spriteWorld_clearTemporary is called
-	List<Sprite> temporarySprites;
+	List<SpriteInstance> temporarySprites;
 	List<ModelInstance> temporaryModels;
 	List<PointLight> temporaryPointLights;
 	List<DirectedLight> temporaryDirectedLights;
@@ -682,40 +713,31 @@ public:
 	}
 };
 
-int sprite_loadTypeFromFile(const String& folderPath, const String& spriteName) {
-	types.pushConstruct(folderPath, spriteName);
-	return types.length() - 1;
-}
-
-int sprite_getTypeCount() {
-	return types.length();
-}
-
 SpriteWorld spriteWorld_create(OrthoSystem ortho, int shadowResolution) {
 	return std::make_shared<SpriteWorldImpl>(ortho, shadowResolution);
 }
 
 #define MUST_EXIST(OBJECT, METHOD) if (OBJECT.get() == nullptr) { throwError("The " #OBJECT " handle was null in " #METHOD "\n"); }
 
-void spriteWorld_addBackgroundSprite(SpriteWorld& world, const Sprite& sprite) {
+void spriteWorld_addBackgroundSprite(SpriteWorld& world, const SpriteInstance& sprite) {
 	MUST_EXIST(world, spriteWorld_addBackgroundSprite);
-	if (sprite.typeIndex < 0 || sprite.typeIndex >= types.length()) { throwError(U"Sprite type index ", sprite.typeIndex, " is out of bound!\n"); }
+	if (sprite.typeIndex < 0 || sprite.typeIndex >= spriteTypes.length()) { throwError(U"Sprite type index ", sprite.typeIndex, " is out of bound!\n"); }
 	// Add the passive sprite to the octree
 	IVector3D origin = sprite.location;
-	IVector3D minBound = origin + types[sprite.typeIndex].minBoundMini;
-	IVector3D maxBound = origin + types[sprite.typeIndex].maxBoundMini;
+	IVector3D minBound = origin + spriteTypes[sprite.typeIndex].minBoundMini;
+	IVector3D maxBound = origin + spriteTypes[sprite.typeIndex].maxBoundMini;
 	world->passiveSprites.insert(sprite, origin, minBound, maxBound);
 	// Find the affected passive region and make it dirty
 	int frameIndex = getSpriteFrameIndex(sprite, world->ortho.view[world->cameraIndex]);
-	const SpriteFrame* frame = &types[sprite.typeIndex].frames[frameIndex];
+	const SpriteFrame* frame = &spriteTypes[sprite.typeIndex].frames[frameIndex];
 	IVector2D upperLeft = world->ortho.miniTilePositionToScreenPixel(sprite.location, world->cameraIndex, IVector2D()) - frame->centerPoint;
 	IRect region = IRect(upperLeft.x, upperLeft.y, image_getWidth(frame->colorImage), image_getHeight(frame->colorImage));
 	world->updatePassiveRegion(region);
 }
 
-void spriteWorld_addTemporarySprite(SpriteWorld& world, const Sprite& sprite) {
+void spriteWorld_addTemporarySprite(SpriteWorld& world, const SpriteInstance& sprite) {
 	MUST_EXIST(world, spriteWorld_addTemporarySprite);
-	if (sprite.typeIndex < 0 || sprite.typeIndex >= types.length()) { throwError(U"Sprite type index ", sprite.typeIndex, " is out of bound!\n"); }
+	if (sprite.typeIndex < 0 || sprite.typeIndex >= spriteTypes.length()) { throwError(U"Sprite type index ", sprite.typeIndex, " is out of bound!\n"); }
 	// Add the temporary sprite
 	world->temporarySprites.push(sprite);
 }

+ 14 - 15
Source/SDK/sandbox/sprite/spriteAPI.h

@@ -23,14 +23,14 @@ inline FVector3D parseFVector3D(const ReadableString& content) {
 
 // A 2D image with depth and normal images for deferred light
 //   To be rendered into images in advance for maximum detail level
-struct Sprite {
+struct SpriteInstance {
 public:
 	int typeIndex;
 	Direction direction;
 	IVector3D location; // Displayed at X, Y-Z in world pixel coordinates
 	bool shadowCasting;
 public:
-	Sprite(int typeIndex, Direction direction, const IVector3D& location, bool shadowCasting)
+	SpriteInstance(int typeIndex, Direction direction, const IVector3D& location, bool shadowCasting)
 	: typeIndex(typeIndex), direction(direction), location(location), shadowCasting(shadowCasting) {}
 };
 
@@ -42,27 +42,27 @@ DenseModel DenseModel_create(const Model& original);
 //   To be rendered during game-play to allow free rotation
 struct ModelInstance {
 public:
-	DenseModel visibleModel;
-	Model shadowModel;
+	int typeIndex;
 	Transform3D location; // 3D tile coordinates with translation and 3-axis rotation allowed
 public:
-	// The shadowCasting property is replaced by multiple constructors
-	ModelInstance(const DenseModel& visibleModel, const Model& shadowModel, const Transform3D& location)
-	: visibleModel(visibleModel), shadowModel(shadowModel), location(location) {}
-	ModelInstance(const DenseModel& visibleModel, const Transform3D& location)
-	: visibleModel(visibleModel), shadowModel(Model()), location(location) {}
+	ModelInstance(int typeIndex, const Transform3D& location)
+	: typeIndex(typeIndex), location(location) {}
 };
 
 class SpriteWorldImpl;
 using SpriteWorld = std::shared_ptr<SpriteWorldImpl>;
 
-int sprite_loadTypeFromFile(const String& folderPath, const String& spriteName);
-int sprite_getTypeCount();
+// Sprite types
+int spriteWorld_loadSpriteTypeFromFile(const String& folderPath, const String& spriteName);
+int spriteWorld_getSpriteTypeCount();
+
+// Model types
+int spriteWorld_loadModelTypeFromFile(const String& folderPath, const String& visibleModelName, const String& shadowModelName);
+int spriteWorld_getModelTypeCount();
 
-// TODO: Create the ortho system using the content of its configuration file to hide the type itself.
 SpriteWorld spriteWorld_create(OrthoSystem ortho, int shadowResolution);
-void spriteWorld_addBackgroundSprite(SpriteWorld& world, const Sprite& sprite);
-void spriteWorld_addTemporarySprite(SpriteWorld& world, const Sprite& sprite);
+void spriteWorld_addBackgroundSprite(SpriteWorld& world, const SpriteInstance& sprite);
+void spriteWorld_addTemporarySprite(SpriteWorld& world, const SpriteInstance& sprite);
 void spriteWorld_addTemporaryModel(SpriteWorld& world, const ModelInstance& instance);
 
 // Create a point light that only exists until the next call to spriteWorld_clearTemporary.
@@ -76,7 +76,6 @@ void spriteWorld_draw(SpriteWorld& world, AlignedImageRgbaU8& colorTarget);
 
 // The result is an approximation in mini-tile units.
 //   The 3D system does not align with screen pixels for less than whole tile units.
-//   TODO: See if an exact float position can be returned from pixelToMiniOffset instead of using integers that are less precise.
 IVector3D spriteWorld_findGroundAtPixel(SpriteWorld& world, const AlignedImageRgbaU8& colorBuffer, const IVector2D& pixelLocation);
 
 // Approximates a mini-tile offset along the ground from the given pixel offset and moves the camera accordingly