I am trying to first render some content to an R8_UNORM image view in subpass 0, and then use the result as an input attachment in subpass 1, using subpassLoad in the fragment shader. However, the final output from the render pass (which is also the swapchain image(s) that is presented) contains garbage (see https://imgur.com/a/RmNdcxg) using the following fragment shader:
#version 420
layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput input_text;
layout(location=0) out vec4 color;
void main()
{
float value = subpassLoad(input_text).r;
if (value > 0.0f) {
color = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}
}
Yet, using this fragment shader:
#version 420
layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput input_text;
layout(location=0) out vec4 color;
void main()
{
float value = subpassLoad(input_text).r;
if (value > 0.0f) {
color = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}
else {
color = vec4(0.0f, 1.0f, 0.0f, 1.0f);
}
}
gives me the expected results (see https://imgur.com/a/JfhuJUk), so I'm suspecting some synchronization issue that isn't present when each fragment is written to.
The clear values used for the two framebuffer attachments are as follows:
VkClearColorValue black_clear_color = { 0.0f, 0.0f, 0.0f, 0.0f };
VkClearColorValue red_clear_color = { 1.0f, 0.0f, 0.0f, 1.0f };
VkClearValue clear_value[2];
clear_value[0].color = vk_black_clear_color; // R8_UNORM attachment
clear_value[1].color = vk_red_clear_color; // Swapchain attachment
with the fragment shader for subpass 0 always writing 1.0f to each fragment it is to shade.
Here's what I deem to be the relevant code:
Command buffer recording, submission, and image presentation
// Record
vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, text_graphics_pipeline);
vkCmdBindVertexBuffers(command_buffer, 0, 1, &text_vertex_buffer, &vertex_buffer_offset);
vkCmdDraw(command_buffer, 15, 1, 0, 0);
vkCmdNextSubpass(command_buffer, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, final_graphics_pipeline);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, final_pipeline_layout, 0, 1, &final_descriptor_set, 0, NULL);
vkCmdBindVertexBuffers(command_buffer, 0, 1, &final_vertex_buffer, &vertex_buffer_offset);
vkCmdDraw(command_buffer, 6, 1, 0, 0);
vkCmdEndRenderPass(command_buffer);
CHECK_VK_RES(vkEndCommandBuffer(command_buffer));
// Submit
VkPipelineStageFlags pipeline_wait_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info;
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = NULL;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &swapchain_image_avilable_semaphore;
submit_info.pWaitDstStageMask = &pipeline_wait_stages;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &command_buffer_finished_semaphore;
CHECK_VK_RES(vkQueueSubmit(queue, 1, &submit_info, command_buffer_finished_fence));
// Present
VkPresentInfoKHR present_info;
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.pNext = NULL;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &command_buffer_finished_semaphore;
present_info.swapchainCount = 1;
present_info.pSwapchains = &swapchain;
present_info.pImageIndices = &image_available_idx;
present_info.pResults = NULL;
CHECK_VK_RES(vkQueuePresentKHR(queue, &present_info));
Render Pass Setup
// Render pass attachments
VkAttachmentDescription vk_render_pass_attachment_descriptions[2];
// Text attachment
render_pass_attachment_descriptions[0].flags = 0;
render_pass_attachment_descriptions[0].format = VK_FORMAT_R8_UNORM;
render_pass_attachment_descriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
render_pass_attachment_descriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
render_pass_attachment_descriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
render_pass_attachment_descriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
render_pass_attachment_descriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
render_pass_attachment_descriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
render_pass_attachment_descriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// Final attachment
render_pass_attachment_descriptions[1].flags = 0;
render_pass_attachment_descriptions[1].format = swapchain_format; // BGRA8_UNORM
render_pass_attachment_descriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
render_pass_attachment_descriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
render_pass_attachment_descriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
render_pass_attachment_descriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
render_pass_attachment_descriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
render_pass_attachment_descriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
render_pass_attachment_descriptions[1].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Subpass descriptions
VkSubpassDescription render_pass_subpass_descriptions[2];
// Subpass 0
VkAttachmentReference text_subpass_color_attachment_reference;
text_subpass_color_attachment_reference.attachment = 0;
text_subpass_color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
render_pass_subpass_descriptions[0].flags = 0;
render_pass_subpass_descriptions[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
render_pass_subpass_descriptions[0].inputAttachmentCount = 0;
render_pass_subpass_descriptions[0].pInputAttachments = NULL;
render_pass_subpass_descriptions[0].colorAttachmentCount = 1;
render_pass_subpass_descriptions[0].pColorAttachments = &text_subpass_color_attachment_reference;
render_pass_subpass_descriptions[0].pResolveAttachments = NULL;
render_pass_subpass_descriptions[0].pDepthStencilAttachment = NULL;
render_pass_subpass_descriptions[0].preserveAttachmentCount = 0;
render_pass_subpass_descriptions[0].pPreserveAttachments = NULL;
// Subpass 1
VkAttachmentReference final_subpass_input_attachment_reference;
final_subpass_input_attachment_reference.attachment = 0;
final_subpass_input_attachment_reference.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkAttachmentReference final_subpass_color_attachment_reference;
final_subpass_color_attachment_reference.attachment = 1;
final_subpass_color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
render_pass_subpass_descriptions[1].flags = 0;
render_pass_subpass_descriptions[1].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
render_pass_subpass_descriptions[1].inputAttachmentCount = 1;
render_pass_subpass_descriptions[1].pInputAttachments = &final_subpass_input_attachment_reference;
render_pass_subpass_descriptions[1].colorAttachmentCount = 1;
render_pass_subpass_descriptions[1].pColorAttachments = &final_subpass_color_attachment_reference;
render_pass_subpass_descriptions[1].pResolveAttachments = NULL;
render_pass_subpass_descriptions[1].pDepthStencilAttachment = NULL;
render_pass_subpass_descriptions[1].preserveAttachmentCount = 0;
render_pass_subpass_descriptions[1].pPreserveAttachments = NULL;
// Subpass dependencies
VkSubpassDependency renderpass_subpass_dependencies[2];
// Ensure subpass 1's color attachment (swapchain image) is transitioned before it's written to
// since the pWaitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
renderpass_subpass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
renderpass_subpass_dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
renderpass_subpass_dependencies[0].srcAccessMask = 0;
renderpass_subpass_dependencies[0].dstSubpass = 1;
renderpass_subpass_dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
renderpass_subpass_dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
renderpass_subpass_dependencies[0].dependencyFlags = 0;
// Subpass 1 cannot read from its input attachment before subpass 0 finishes writing to its color attachment
renderpass_subpass_dependencies[1].srcSubpass = 0;
renderpass_subpass_dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
renderpass_subpass_dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
renderpass_subpass_dependencies[1].dstSubpass = 1;
renderpass_subpass_dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
renderpass_subpass_dependencies[1].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
renderpass_subpass_dependencies[1].dependencyFlags = 0;
// Render pass
VkRenderPassCreateInfo render_pass_info;
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.pNext = NULL;
render_pass_info.flags = 0;
render_pass_info.attachmentCount = 2;
render_pass_info.pAttachments = render_pass_attachment_descriptions;
render_pass_info.subpassCount = 2;
render_pass_info.pSubpasses = render_pass_subpass_descriptions;
render_pass_info.dependencyCount = 2;
render_pass_info.pDependencies = renderpass_subpass_dependencies;
VkRenderPass render_pass;
CHECK_VK_RES(vkCreateRenderPass(vk_device, &render_pass_info, NULL, &render_pass));
Any help would be greatly appreciated, as I can't seem to see what's wrong here.
The synchronization was not the culprit here. Rather, it was the fact that the out vec4 color from subpass 1 was always written out to the framebuffer (as the geometry is a full-screen quad), even though it wasn't initialized to anything if the if-condition wasn't satisfied.
By adding a discard to the shader in the else-clause, the correct output is obtained:
#version 420
layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput input_text;
layout(location=0) out vec4 color;
void main()
{
float value = subpassLoad(input_text).r;
if (value > 0.0f) {
color = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}
else {
discard;
}
}
As an "fun" fact: the reason the incorrect image in the question isn't very blue in the top-left corner, but is blue for most of the rest of the image is probably that the registers that are used to store the values in color are re-used. So in the beginning (assuming the work starts from the top-left corner), the registers contain garbage, but as actual blue fragments are written out, more and more registers have old blue values in them. Therefore, when the else-clause is active, blue is written out, even though its technically garbage.
I have one issue left with ASSIMP DIRECT X C++ ANIMATION WITH SKELETON.
for (UINT m = 0; m < currentMesh->mBones[k]->mNumWeights; m++) //verticer som påverkas
{
vertexVector[k].joints.x = currentMesh->mBones[k]->mWeights[m].mVertexId;
That code shows all vertices affected by a bone - k, inside an iteration.
All of these vertices must have the same vert ID since they are all affected by the same bone/joint.
The problem is, I need to make a list of every vertex and a list of every indice of a face, where I store position, UV, Normal etc.
The list that displays all of the vertices, is not in the same order obviously as the lists that displays all the vertices affected by each bone.
So how can I combine these lists?
"vertexVector"... etc is an example of a list with jointInfo that is corresponding to vertexID.
It has room for more places and another variable for the weight.
But that list doesn't work obviously.
What am I doing wrong with Assimp? Hope this was a clear post.
UPdate this is how i build the matrices: I don't know what is wrong.
void jointTransform(float
timeInSeconds, std::vector<DirectX::XMMATRIX>& transformM, aiAnimation*
ani, UINT nrOfJoints, std::vector<joints>& jointInfo, const aiScene*
scenePtr)
{
DirectX::XMMATRIX iD = DirectX::XMMatrixIdentity();
float ticksPerSecond = (float)ani->mTicksPerSecond;
if (ticksPerSecond == 0)
{
ticksPerSecond = 30;
}
float timeInTicks = timeInSeconds * ticksPerSecond;
float animationTime = fmod(timeInTicks, (float)ani->mDuration);
readNodeHeiarchy(animationTime, scenePtr->mRootNode, iD, jointInfo, ani,
scenePtr);
transformM.resize(nrOfJoints);
for (UINT i = 0; i < transformM.size(); i++)
{
transformM[i] = jointInfo[i].transformFinal;
}
}
void readNodeHeiarchy(float time, const aiNode* node, DirectX::XMMATRIX
parentMat, std::vector<joints>& jointInfo, aiAnimation* ani, const
aiScene* scenePtr)
{
std::string nodeNameString = node->mName.data;
//Skapa en parentTransform från noden. Som sedan skickas in som parent
matris, första gången är det identitetsmatrisen.
aiMatrix4x4 nodeTransform = node->mTransformation;
DirectX::XMMATRIX combined;
combined = DirectX::XMMatrixSet(nodeTransform.a1, nodeTransform.a2,
nodeTransform.a3, nodeTransform.a4,
nodeTransform.b1, nodeTransform.b2, nodeTransform.b3, nodeTransform.b4,
nodeTransform.c1, nodeTransform.c2, nodeTransform.c3, nodeTransform.c4,
nodeTransform.d1, nodeTransform.d2, nodeTransform.d3,
nodeTransform.d4);
const aiNodeAnim* joint = nullptr;
//Kolla om noden är ett ben.
for (UINT i = 0; i < ani->mNumChannels; i++)
{
if (nodeNameString == ani->mChannels[i]->mNodeName.data)
{
joint = ani->mChannels[i];
}
}
DirectX::XMMATRIX globalTransform = DirectX::XMMatrixIdentity();
//om den är ett ben så är joint inte längre nullptr, den blir det benet.
if (joint)
{
DirectX::XMMATRIX S;
DirectX::XMMATRIX R;
DirectX::XMMATRIX T;
//scale
aiVector3D scaleV;
calcLerpScale(scaleV, time, joint);
S = DirectX::XMMatrixScaling(scaleV.x, scaleV.y, scaleV.z);
//rotate
aiQuaternion rotationQ;
calcLerpRot(rotationQ, time, joint);
DirectX::XMVECTOR q;
q = DirectX::XMVectorSet(rotationQ.x, rotationQ.y, rotationQ.z,
rotationQ.w);
R = DirectX::XMMatrixRotationQuaternion(q);
//translate
aiVector3D transV;
calcLerpTrans(transV, time, joint);
T = DirectX::XMMatrixTranslation(transV.x, transV.y, transV.z);
combined = S * R * T;
globalTransform = combined * parentMat;
}
//DirectX::XMMATRIX globalTransform = combined * parentMat;
//if (jointInfo[jointInfo.size() - 1].name.C_Str() != nodeNameString)
//{
for (UINT i = 0; i < jointInfo.size(); i++)
{
if (jointInfo[i].name.C_Str() == nodeNameString)
{
OutputDebugStringA("\n");
OutputDebugStringA(jointInfo[i].name.C_Str());
OutputDebugStringA("\n");
aiMatrix4x4 off = jointInfo[i].offsetM;
DirectX::XMMATRIX offset;
offset = DirectX::XMMatrixSet(off.a1, off.a2, off.a3, off.a4,
off.b1, off.b2, off.b3, off.b4,
off.c1, off.c2, off.c3, off.c4,
off.d1, off.d2, off.d3, off.d4);
DirectX::XMMATRIX rootMInv;
aiMatrix4x4 rootInv = scenePtr->mRootNode-
>mTransformation.Inverse();
rootMInv = DirectX::XMMatrixSet(rootInv.a1, rootInv.a2,
rootInv.a3, rootInv.a4,
rootInv.b1, rootInv.b2, rootInv.b3, rootInv.b4,
rootInv.c1, rootInv.c2, rootInv.c3, rootInv.c4,
rootInv.d1, rootInv.d2, rootInv.d3, rootInv.d4);
jointInfo[i].transformFinal = offset * globalTransform *
rootMInv;
break;
}
}
//}
for (UINT i = 0; i < node->mNumChildren; i++)
{
readNodeHeiarchy(time, node->mChildren[i], globalTransform, jointInfo,
ani, scenePtr);
}
}
void calcLerpScale(aiVector3D& scale, float aniTime, const aiNodeAnim*
joint)
{
if (joint->mNumScalingKeys == 1)
{
scale = joint->mScalingKeys[0].mValue;
return;
}
UINT scaleInd = findIndexS(aniTime, joint);
UINT nextScale = scaleInd + 1;
assert(nextScale < joint->mNumScalingKeys);
float deltaTime = (float)joint->mScalingKeys[nextScale].mTime -
(float)joint->mScalingKeys[scaleInd].mTime;
float factor = (aniTime - (float)joint->mScalingKeys[scaleInd].mTime) /
deltaTime;
assert(factor >= 0.0f && factor <= 1.0f);
const aiVector3D& startScaleV = joint->mScalingKeys[scaleInd].mValue;
const aiVector3D& endScaleV = joint->mScalingKeys[nextScale].mValue;
//interpolate
aiVector3D Delta = endScaleV - startScaleV; // längden
scale = startScaleV + (factor * Delta); //gå ett antal steg beroende på
faktorn mellan start och slut.
scale.Normalize();
}
void calcLerpRot(aiQuaternion& rotation, float aniTime, const aiNodeAnim*
joint)
{
if (joint->mNumRotationKeys == 1)
{
rotation = joint->mRotationKeys[0].mValue;
return;
}
UINT rotIndex = findIndexRot(aniTime, joint);
UINT nextRot = (rotIndex + 1);
assert(nextRot < joint->mNumRotationKeys);
float deltaTime = (float)joint->mRotationKeys[nextRot].mTime -
(float)joint->mRotationKeys[rotIndex].mTime;
float factor = (aniTime - (float)joint->mRotationKeys[rotIndex].mTime) /
deltaTime;
assert(factor >= 0.0f && factor <= 1.0f);
const aiQuaternion& StartRotationQ = joint->mRotationKeys[rotIndex].mValue;
const aiQuaternion& EndRotationQ = joint->mRotationKeys[nextRot].mValue;
aiQuaternion::Interpolate(rotation, StartRotationQ, EndRotationQ, factor);
rotation.Normalize();
}
void calcLerpTrans(aiVector3D& translation, float aniTime, const
aiNodeAnim*
joint)
{
if (joint->mNumPositionKeys == 1)
{
translation = joint->mPositionKeys[0].mValue;
return;
}
UINT transIndex = findIndexT(aniTime, joint);
UINT nextTrans = (transIndex + 1);
assert(nextTrans < joint->mNumPositionKeys);
float deltaTime = (float)joint->mPositionKeys[nextTrans].mTime -
(float)joint->mPositionKeys[transIndex].mTime;
float factor = (aniTime - (float)joint->mPositionKeys[transIndex].mTime) /
deltaTime;
assert(factor >= 0.0f && factor <= 1.0f);
const aiVector3D& startTransV = joint->mPositionKeys[transIndex].mValue;
const aiVector3D& endTransV = joint->mPositionKeys[nextTrans].mValue;
//interpolate
aiVector3D Delta = endTransV - startTransV;
translation = startTransV + (factor * Delta);
translation.Normalize();
}
UINT findIndexRot(float aniTime, const aiNodeAnim* joint)
{
assert(joint->mNumRotationKeys > 0);
for (UINT i = 0; i < joint->mNumRotationKeys - 1; i++)
{
if (aniTime < (float)joint->mRotationKeys[i + 1].mTime)
{
return i;
}
}
assert(0);
}
}
Not sure what you mean by "All of these vertices must have the same vert ID" - the vertex id's of the k:th bone, according to mBones[k]->mWeights[..].mVertexId, are indices to vertices influenced by this bone, and they are going to be different (otherwise there would be either redundancy of conflict).
You probably want to have bone indices and bone weights as part of the vertex definition for easy handling in a shader. Something like
struct vertex (
vec3 pos;
vec3 normal;
float bone_weights[N]; // weights of bones influencing this vertex
unsigned bone_indices[N]; // indices of bones influencing this vertex
}
std::vector<vertex> mesh_vertices;
Where N is the maximum number of influence bones per vertex. A common value is four, but this depends on the mesh your are importing.
Based on your example, a rough draft could be something like this:
// k:th bone of bones in currentMesh
for (UINT m = 0; m < currentMesh->mBones[k]->mNumWeights; m++)
{
float bone_weight = currentMesh->mBones[k]->mWeights[m].mWeight;
unsigned vertex_index = currentMesh->mBones[k]->mWeights[m].mVertexId;
mesh_vertices[vertex_index].bone_weights[m] = bone_weight;
mesh_vertices[vertex_index].bone_indices[m] = k;
}
Here we've assumed that mNumWeights = N, but this needs to checked, as mentioned.
I'm creating a mobius strip in Java3D. I've got a seam and I can't seem to get rid of it! I'm assuming it's got to do with normals and the fact that the difference in angle between the conjoined edges is technically 180. Can anyone help me remove this seam?
Here's my code:
public class MobiusStrip extends Applet {
public static void main(String[] args){
new MainFrame(new MobiusStrip(), 800, 600);
}
#Override
public void init(){
GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas = new Canvas3D(gc);
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
SimpleUniverse su = new SimpleUniverse(canvas);
su.getViewingPlatform().setNominalViewingTransform();
BranchGroup bg = createSceneGraph();
bg.compile();
su.addBranchGraph(bg);
}
private BranchGroup createSceneGraph(){
BranchGroup root = new BranchGroup();
Shape3D shape = new Shape3D();
shape.setGeometry(mobius().getIndexedGeometryArray());
//Scaling transform
Transform3D tr = new Transform3D();
tr.setScale(0.5);
//Spin transform group
TransformGroup spin = new TransformGroup();
spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
root.addChild(spin);
//Set appearance
Appearance ap = new Appearance();
PointAttributes pa = new PointAttributes(10, true);
ap.setPointAttributes(pa);
ap.setPolygonAttributes(new PolygonAttributes
(PolygonAttributes.POLYGON_FILL,
PolygonAttributes.CULL_NONE, 0));
//Set materials
Material mat = new Material();
mat.setLightingEnable(true);
mat.setShininess(30);
ap.setMaterial(mat);
//Overarching Transform group
TransformGroup tg = new TransformGroup(tr);
tg.addChild(shape);
spin.addChild(tg);
shape.setAppearance(ap);
//Set rotation
Alpha alpha = new Alpha(-1, 6000);
RotationInterpolator rotate = new RotationInterpolator(alpha, spin);
BoundingSphere bounds = new BoundingSphere();
rotate.setSchedulingBounds(bounds);
spin.addChild(rotate);
//Set background
Background background = new Background(1.0f, 1.0f, 1.0f);
background.setApplicationBounds(bounds);
root.addChild(background);
//Set lighting
AmbientLight light = new AmbientLight(true, new Color3f(Color.BLACK));
light.setInfluencingBounds(bounds);
root.addChild(light);
PointLight ptlight = new PointLight(new Color3f(Color.white),
new Point3f(0.5f,0.5f,1f),
new Point3f(1f,0.2f,0f));
ptlight.setInfluencingBounds(bounds);
root.addChild(ptlight);
return root;
}//Close branchgroup method
//Create the Mobius shape
private GeometryInfo mobius()
{
int m = 100; //number of row points
int n = 100; //number of col points
int p = 4*((m-1)*(n-1)); //faces * points per face
IndexedQuadArray iqa = new IndexedQuadArray(m*n,
GeometryArray.COORDINATES, p);
Point3d[] vertices = new Point3d[m*n];
int index = 0;
//Create vertices
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
double u = i * (2*(Math.PI))/(m - 1);
double v = -0.3 + (j * (0.6/(n-1)));
double x=(1+v*Math.cos(u/2))*Math.cos(u);
double y=(1+v*Math.cos(u/2))*Math.sin(u);
double z=v*Math.sin(u/2);
vertices[index]=new Point3d(x,y,z);
index++;
}//close nested for loop
}//close for loop
iqa.setCoordinates(0, vertices);
index = 0;
//set index for coordinates
for(int i = 0; i < m-1; i++){
for(int j = 0; j < n-1; j++){
iqa.setCoordinateIndex(index, i*m+j);
index++;
iqa.setCoordinateIndex(index, i*m+j+1);
index++;
iqa.setCoordinateIndex(index, (i+1)*m+j+1);
index++;
iqa.setCoordinateIndex(index, (i+1)*m+j);
index++;
}//close nested for loop
}//close for loop
//create geometry info and generate normals for shape
GeometryInfo gi = new GeometryInfo(iqa);
NormalGenerator ng = new NormalGenerator();
ng.generateNormals(gi);
return gi;
}
}
See this question for more explanation. You'll need two changes:
ap.setPolygonAttributes(new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_BACK, 0));
double u = i * (4 * (Math.PI)) / (m - 1);
Alas this is a university type question, but one im struggling with none the less.
I need to generate large test sets of rectangles, non-overlapping, that can then be used to test an algorithm that finds the adjacencies between them. The test sets will need to probably have upwards of 10000 - 100000 rectangles in them. Ive been searching the net for examples of how to generate test sets like this, but have come up with nothing. Im aware that I could use a purely brute force method, and every time a random rectangle is generated, check whether or not it overlaps with any of the other rectangles, but this seems like generating the test sets will end up taking days if not longer!
Any one know how to go about doing this, or at least where I should start looking?
I found your idea fun and challanging and therefore tried a solution by using a matrix.
Basicly, the idea is (when talking in pixels) to create a matrix of booleans of the same width and height as the square root of MaxWidthOfRectangle * (NumberOfRectangles) (just for simplicity the same width and height).
Next, for each entry in the matrix, generate a random rectangle between min and max bounds. and set all the bools in the matrix for the specific rectangle. Now when generating the next rectangle, you can simply check "around" the desired location to determine how much space you can occupy rather then having to estimate a size and compare with each other rectangle if it conflicts.
My code:
class RectangleGenerator
{
readonly bool[,] _matrix;
readonly int _size;
readonly int _minimalBoxSize;
readonly int _maximumBoxSize;
readonly Random _random = new Random(1);
readonly List<Point> _offsets;
public bool[,] Matrix { get { return _matrix; } }
public RectangleGenerator(int size, int minimalBoxSize, int maximumBoxSize)
{
_matrix = new bool[size, size];
_size = size;
_minimalBoxSize = minimalBoxSize;
_maximumBoxSize = maximumBoxSize;
_offsets = new List<Point>(size * size);
Reset();
}
public IEnumerable<Rectangle> Calculate()
{
while (_offsets.Count > 0)
{
Point currentPoint = _offsets[_offsets.Count - 1];
_offsets.RemoveAt(_offsets.Count - 1);
if (!_matrix[currentPoint.X, currentPoint.Y])
{
Rectangle rectangle;
if (TryCreateNextRectangle(currentPoint.X, currentPoint.Y, out rectangle))
{
// fill the matrix with the rectangle + padding
int startFillX = Math.Max(0, rectangle.Left);
int startFillY = Math.Max(0, rectangle.Top);
int endFillX = Math.Min(_size, rectangle.Right);
int endFillY = Math.Min(_size, rectangle.Bottom);
for (int fillX = startFillX; fillX < endFillX; fillX++)
for (int fillY = startFillY; fillY < endFillY; fillY++)
{
_matrix[fillX, fillY] = true;
}
yield return rectangle;
}
}
}
}
private bool TryCreateNextRectangle(int x, int y, out Rectangle rectangle)
{
int maxWidth = DetermineMaxWidth(x, y, _minimalBoxSize);
int maxHeight = DetermineMaxHeight(y, x, maxWidth);
if (maxWidth < _minimalBoxSize || maxHeight < _minimalBoxSize)
{
rectangle = Rectangle.Empty;
return false;
}
int width = _random.Next(_minimalBoxSize, maxWidth);
int height = _random.Next(_minimalBoxSize, maxHeight);
rectangle = new Rectangle(x, y, width, height);
return true;
}
private int DetermineMaxWidth(int x, int y, int height)
{
int result = Math.Min(_maximumBoxSize, _size - x);
for (int offsetX = 0; offsetX < result; offsetX++)
for (int offsetY = 0; offsetY < height; offsetY++)
{
if (_matrix[x + offsetX, y + offsetY])
{
result = offsetX;
break;
}
}
return result;
}
private int DetermineMaxHeight(int y, int x, int width)
{
int result = Math.Min(_maximumBoxSize, _size - y);
for (int offsetY = 0; offsetY < result; offsetY++)
for (int offsetX = 0; offsetX < width; offsetX++ )
{
if (_matrix[x + offsetX, y + offsetY])
{
result = offsetY;
break;
}
}
return result;
}
public void Reset()
{
// append for padding:
for (int x = 0; x < _size; x++)
for (int y = 0; y < _size; y++)
{
_matrix[x, y] = false;
if (_size - x >= _minimalBoxSize && _size - y >= _minimalBoxSize)
{
_offsets.Add(new Point(x, y));
}
}
_offsets.Sort((x, y) => x == y ? 0 : _random.Next(-1, 1));
}
}
So I've been working in Processing for a few weeks now, and, though I'm not experienced in programming, I have moved on to more complex projects. I'm programming an evolution simulator, that spawns creatures with random properties.
Eventually, I'll add reproduction, but as of now the creatures just sort of float around the screen, and follow the mouse somewhat. It interacts with sound from the line in, but I commented those parts out so that it can be viewed on the canvas, it shouldn't really change the question, I just thought I would point it out.
As of now, the framerate is far less than ideal for me, and it slowly lowers as more creatures are spawned. Am I making some fundamental mistake, or am I just running too many functions per frame?
Here's the source code, and you can play with it in the browser here:
//import ddf.minim.*;
//import ddf.minim.signals.*;
//import ddf.minim.analysis.*;
//import ddf.minim.effects.*;
//Minim minim;
//AudioInput in;
boolean newCreature = true;
boolean matured[];
int ellipses[];
int hair[];
int maxCreatureNumber = 75;
//int volume;
//int volumeTolerance = 1;
int creatureIndex = -1;
int creatureX[];
int creatureY[];
float strokeWeightAttribute[];
float creatureSize[];
float creatureEndSize[];
float creatureXIncrement[];
float creatureYIncrement[];
float bubbleSize;
float easing = 0.05;
float angle = 0.00;
color colorAttribute[];
void setup() {
background(0);
size(1000,500);
noFill();
//minim = new Minim(this);
//in = minim.getLineIn(Minim.STEREO, 512);
creatureX = new int[maxCreatureNumber];
creatureY = new int[maxCreatureNumber];
ellipses = new int[maxCreatureNumber];
hair = new int[maxCreatureNumber];
strokeWeightAttribute = new float[maxCreatureNumber];
creatureEndSize = new float[maxCreatureNumber];
creatureSize = new float[maxCreatureNumber];
creatureXIncrement = new float[maxCreatureNumber];
creatureYIncrement = new float[maxCreatureNumber];
matured = new boolean[maxCreatureNumber];
colorAttribute = new color[maxCreatureNumber];
}
void draw() {
angle += 0.05;
fill(0, 50);
rect(-1, -1, 1001, 501);
// for(int i = 0; i < in.bufferSize() - 1; i++) {
// if(in.mix.get(i) * 50 > volumeTolerance) {
// volume++;
// }
// }
if(newCreature && creatureIndex < maxCreatureNumber - 1) {
initSpontaneousCreature();
}
updateCreatures();
// bubbleSize = volume/250;
bubbleSize += 0.01;
// volume = 0;
}
//void stop() {
// minim.stop();
// super.stop();
//}
void initSpontaneousCreature() {
creatureIndex++;
creatureEndSize[creatureIndex] = int(random(5, 20));
creatureX[creatureIndex] = int(random(1000));
if(creatureX[creatureIndex] >= 500) {
creatureX[creatureIndex] -= creatureEndSize[creatureIndex];
}
else {
creatureX[creatureIndex] += creatureEndSize[creatureIndex];
}
creatureY[creatureIndex] = int(random(500));
if(creatureY[creatureIndex] >= 250) {
creatureY[creatureIndex] -= creatureEndSize[creatureIndex];
}
else {
creatureY[creatureIndex] += creatureEndSize[creatureIndex];
}
ellipses[creatureIndex] = int(random(4));
hair[creatureIndex] = int(random(4));
strokeWeightAttribute[creatureIndex] = random(1, 4);
colorAttribute[creatureIndex] = color(int(random(20,255)), int(random(20,255)), int(random(20,255)));
matured[creatureIndex] = false;
newCreature = false;
while(ellipses[creatureIndex] == 0 && hair[creatureIndex] == 0) {
ellipses[creatureIndex] = int(random(4));
hair[creatureIndex] = int(random(4));
}
}
void updateCreatures() {
for(int n = 0; n <= creatureIndex; n++) {
if(matured[n]) {
creatureX[n] += ((((mouseX) - creatureX[n]) * easing) / (60/*-abs(volume/5))*/)) + random(-5, 6);
creatureY[n] += ((((mouseY) -creatureY[n]) * easing) / (60/*-abs(/*volume/5))*/)) + random(-5,6);
drawCreature();
}
else {
if(creatureEndSize[n] != creatureSize[n]) {
creatureSize[n] += bubbleSize;
if(creatureSize[n] > creatureEndSize[n]) {
creatureSize[n] -= (creatureSize[n] - creatureEndSize[n]);
}
}
else {
newCreature = true;
matured[n] = true;
// bubbleSize = 0;
}
drawCreature();
}
}
}
void drawCreature() {
for(int n = 0; n <= creatureIndex; n++) {
if(matured[n]) {
stroke(colorAttribute[n]);
strokeWeight(strokeWeightAttribute[n]);
for(int i = 0; i <= 4; i++) {
if(ellipses[n] == i) {
if(i == 0) {
}
else if (i == 1) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
else if(i == 2) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(270));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
else if(i == 3) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(90));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(180));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
rotate(radians(270));
ellipse(creatureSize[n], creatureSize[n], creatureSize[n], creatureSize[n]);
popMatrix();
}
}
if(hair[n] == i) {
if(i == 0) {
}
else if (i == 1) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=70) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
else if(i == 2) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=30) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
else if(i == 3) {
pushMatrix();
translate(creatureX[n], creatureY[n]);
for(int j = 0; j <= 360; j+=1) {
rotate(j);
stroke(colorAttribute[n], random(255));
line(0,0, creatureSize[n] + random(10), creatureSize[n] + random(10));
}
popMatrix();
}
}
}
}
if(!matured[n]) {
stroke(abs(sin(angle) * 255));
//strokeWeight(5);
ellipse(creatureX[n], creatureY[n], creatureSize[n] * 5, creatureSize[n] * 5);
noStroke();
}
}
}
Right, as I suspected, all the unnecessary pushMatrix(), popMatrix() calls and the large amount of lines seemed to be the main culprits, still, there was a lot of redundant code.
I simply refactored the code in a cleaner manner and it seems to run fine.
Here is my 'improved' version:
int maxCreatures = 75;
int numCreatures = 0;
int spawnNthFrame = 50;//spawn a creature every 50 frames
Creature[] creatures;
void setup() {
background(0);
size(1000,500);
noFill();
creatures = new Creature[maxCreatures];
}
void draw() {
fill(0, 50);
rect(-1, -1, 1001, 501);
if(frameCount % spawnNthFrame == 0){
println("creatures: " + numCreatures);
if(numCreatures < maxCreatures) {
//Creature constructor float endSize,int x, int y,int ellipses,int hair,float strokeW,color c
creatures[numCreatures] = new Creature(random(5, 20),int(random(1000)),int(random(500)),int(random(4)),int(random(4)),random(1, 4),color(int(random(20,255)), int(random(20,255)), int(random(20,255))));
numCreatures++;
}
}
for(int i = 0; i < numCreatures; i++) creatures[i].update();
}
and the Creature class:
class Creature{
int x,y,cXInc,cYInc;//if x,y are ints, increments would be into, right?
float cStrokeWeight,cSize,cEndSize,cSizeInc = 0.01,easing = 0.05,angle = 0.00;
color cColor;
int hair,numHair,ellipses;
boolean matured = false;
Creature(float endSize,int x, int y,int ellipses,int hair,float strokeW,color c){
cEndSize = endSize;
this.x = x;
if(x >= 500) x -= cEndSize;
else x += cEndSize;
this.y = y;
if(y >= 250) x -= cEndSize;
else x += cEndSize;
this.ellipses = ellipses;
this.hair = hair;
if(hair == 1) numHair = 3;//~5, half that, draw through centre, etc.
if(hair == 2) numHair = 6;
if(hair == 3) numHair = 30;//no default value
cStrokeWeight = strokeW;
this.cColor = c;
}
void update(){
if(matured) {
x += (((mouseX - x) * easing) / 60) + random(-5, 6);
y += (((mouseY - y) * easing) / 60) + random(-5, 6);
}else {
if(cSize < cEndSize) cSize += cSizeInc;
else matured = true;
angle += 0.05;
}
this.draw();
}
void draw(){
if(matured){
stroke(cColor);
strokeWeight(cStrokeWeight);
if(ellipses == 1){//2 ellipses diagonally
ellipse(x,y,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
if(ellipses == 2){
ellipse(x,y,cSize,cSize);
ellipse(x,y+cSize,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
if(ellipses == 3){
ellipse(x,y,cSize,cSize);
ellipse(x+cSize,y,cSize,cSize);
ellipse(x,y+cSize,cSize,cSize);
ellipse(x+cSize,y+cSize,cSize,cSize);
}
float hairAngleInc = TWO_PI/numHair;//angle increment for each piece = 360/number of hair lines
float hairAngle,hairLength,hairCos,hairSin;
for(int i = 0; i < numHair; i++){
hairAngle = hairAngleInc * i;
hairCos = cos(hairAngle);
hairSin = sin(hairAngle);
hairLength = random(20);
stroke(cColor, random(255));
line(x + (hairCos * -hairLength),y + (hairSin * -hairLength), x + (hairCos * hairLength),y + (hairSin * hairLength));
}
}else{
stroke(abs(sin(angle) * 255));
ellipse(x,y, cSize * 5, cSize * 5);
}
}
}
Ok, now for the explanations.
First, I separated all the variables that were related to one creature from the 'global' ones that determine how the sketch runs (how many creatures get spawned, etc.).
This makes the main code about 25 lines long and altogether a bit below 100 lines which is less than half of the original.
The first part doesn't do anything special. In the draw() function, instead of creating a Creature every frame, I draw one every Nth frame using the spawnNthFrame variable, this made it easy to see which state of the creature made it slow. If you set a small number like 2 to that variable it should spawn a lot of creatures per frame.
The Creature class has all the properties the original code stored in arrays.
Instead of doing
pushMatrix();
translate();
ellipse();
rotate()
ellipse()
popMatrix();
I simply draw the ellipses at x,y.
A little hint on the rotations. I've noticed they were increments
of 90 degrees. Processing has some nice constants for 90,180,360 degrees
in radians: HALF_PI, PI, TWO_PI which can be handy sometimes.
Now for the 'hairy' situation, here's something I commented out for myself:
//if(i == 1) for(int j = 0; j <= 360; j+=70) , well 360/70 is about 5, if (i == 2) , 12 hair
//if = 3-> 360 lines ? do you really need that many lines, that thick ? how about 30 ? 5*12=60, but if you draw the lines through the centre, not from the centre, you can get away with half the lines
So there were 3 loops for drawing lines, each having different increments. Basically
there were either 360/70 lines, 360/30 lines and 360 lines.
Roughly about 5,12 and 360 lines. About the 5,12 lines, I kind of halved that by drawing 'diameter' lines across the centre as opposed to 'radius' lines from the centre.
Here's what I mean,
Also I think that 360 lines with that strokeWeight and the jittery motion will probably look like a bunch of lines hard to count, so I thought, why split hairs? :P
Maybe the creature will look pretty similar with about 60 radii which means 30 diameters.
Now to explain a bit of the trig functions used for this.
The main thing is the 'polar to cartesian' coordinates conversion:
Polar would be something like:
"I am moving on a circle to a direction described by an angle (much like one handle of a clock) and radius (distance from centre)."
and Cartesian
"I'm moving based on two axes (horizontal/X and vertical/Y), kind of like the streets of Manhattan, but I cheat and also move diagonally through walls."
If that makes any sense... :)
Anyway, you convert the angle and radius pair to the x and y pair using the formula:
x = cos(angle) * radius
y = sin(angle) * radius
For each line:
angle = hairAngle
radius = hairLength
So the line() with *x + (hairCos * -hairLength)* looks a bit like this:
x + (hairCos * -hairLength) =
move to x and from there move by hairLength
to the left(-) for the current angle (hairCos)
Similar for y, but using cos, so this puts the first point of the line in the opposite direct (-hairLength) of the angle moving from the centre (which is the Creature's x) and the second is 'diagonal'. Imagine drawing 'diagonals' (from (-x,-y) to (+x,+y)), but you also rotate these.
Update
Apparently copy/pasting this code works in javascript too (best viewed in Chromium/Chrome). You can also run it right here:
var maxCreatures = 75;
var numCreatures = 0;
var spawnNthFrame = 50;//spawn a creature every 50 frames
var creatures = [];
function setup() {
background(0);
createCanvas(1000,500);
noFill();
}
function draw() {
fill(0, 50);
rect(-1, -1, 1001, 501);
if(frameCount % spawnNthFrame === 0){
println("creatures: " + numCreatures);
if(numCreatures < maxCreatures) {
//Creature constructor float endSize,int x, int y,int ellipses,int hair,float strokeW,color c
creatures[numCreatures] = new Creature(random(5, 20),int(random(1000)),int(random(500)),int(random(4)),int(random(4)),random(1, 4),color(int(random(20,255)), int(random(20,255)), int(random(20,255))));
numCreatures++;
}
}
for(var i = 0; i < numCreatures; i++) creatures[i].update();
}
function Creature(endSize,x,y,ellipses,hair,strokeW,c){
this.x = x;
this.y = y;
this.ellipses = ellipses;
this.hair = hair;
this.numHair = 0;
this.cStrokeWeight = strokeW;
this.cColor = c;
this.cXInc = 0;
this.cYInc = 0.01;
this.cSize = 0;
this.cEndSize = endSize;
this.easing = 0.05;
this.angle = 0.0;
this.matured = false;
if(x >= 500) x -= this.cEndSize;
else x += this.cEndSize;
if(y >= 250) x -= this.cEndSize;
else x += this.cEndSize;
if(hair == 1) this.numHair = 3;//~5, half that, draw through centre, etc.
if(hair == 2) this.numHair = 6;
if(hair == 3) this.numHair = 30;//no default value
this.update = function(){
if(this.matured) {
this.x += (((mouseX - this.x) * this.easing) / 60) + random(-5, 6);
this.y += (((mouseY - this.y) * this.easing) / 60) + random(-5, 6);
}else {
if(this.cSize < this.cEndSize) this.cSize += this.cSizeInc;
else this.matured = true;
this.angle += 0.05;
}
this.draw();
}
this.draw = function(){
if(this.matured){
stroke(this.cColor);
strokeWeight(this.cStrokeWeight);
if(this.ellipses == 1){//2 ellipses diagonally
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
if(this.ellipses == 2){
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x,this.y+this.cSize,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
if(this.ellipses == 3){
ellipse(this.x,this.y,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y,this.cSize,this.cSize);
ellipse(this.x,this.y+this.cSize,this.cSize,this.cSize);
ellipse(this.x+this.cSize,this.y+this.cSize,this.cSize,this.cSize);
}
var hairAngleInc = TWO_PI/this.numHair;//angle increment for each piece = 360/number of hair lines
var hairAngle,hairLength,hairCos,hairSin;
for(var i = 0; i < this.numHair; i++){
hairAngle = hairAngleInc * i;
hairCos = cos(hairAngle);
hairSin = sin(hairAngle);
hairLength = random(20);
stroke(this.cColor, random(255));
line(this.x + (hairCos * -hairLength),this.y + (hairSin * -hairLength), this.x + (hairCos * hairLength),this.y + (hairSin * hairLength));
}
}else{
stroke(abs(sin(this.angle) * 255));
ellipse(this.x,this.y, this.cSize * 5, this.cSize * 5);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js"></script>
You could use the frameRate(fps)function. What it does is, it specifies the number of frames to be displayed every second. However, If the processor is not fast enough to maintain the specified rate, it will not be achieved. For example, the function call frameRate(30) will attempt to refresh 30 times a second. It is recommended to set the frame rate within setup().
Remember, using draw() without specifying the frame rate, by default it will run at 60 fps.
Well, there's the good old random-pause method. It's the "poor man's profiler".
Just snapshot it a few times. That will show you exactly what's taking the most time. Those are the things you should see if you can make faster.
It will show up in increased framerate.