/*
* This file is part of Goko.
*
* Goko is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Goko is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Goko. If not, see <http://www.gnu.org/licenses/>.
*/
package org.goko.core.gcode.rs274ngcv3.jogl.renderer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.media.opengl.GL;
import javax.media.opengl.GL3;
import javax.vecmath.Color4f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.goko.core.common.exception.GkException;
import org.goko.core.common.measure.quantity.AngleUnit;
import org.goko.core.common.utils.IIdBean;
import org.goko.core.controller.IFourAxisControllerAdapter;
import org.goko.core.controller.IGCodeContextProvider;
import org.goko.core.gcode.element.IGCodeProvider;
import org.goko.core.gcode.element.IInstructionSetIterator;
import org.goko.core.gcode.execution.ExecutionToken;
import org.goko.core.gcode.execution.ExecutionTokenState;
import org.goko.core.gcode.rs274ngcv3.context.GCodeContext;
import org.goko.core.gcode.rs274ngcv3.element.InstructionProvider;
import org.goko.core.gcode.rs274ngcv3.instruction.AbstractInstruction;
import org.goko.core.gcode.rs274ngcv3.jogl.internal.Activator;
import org.goko.core.gcode.rs274ngcv3.jogl.renderer.colorizer.IInstructionColorizer;
import org.goko.core.gcode.rs274ngcv3.jogl.renderer.colorizer.MotionModeColorizer;
import org.goko.core.gcode.service.IGCodeExecutionListener;
import org.goko.core.log.GkLog;
import org.goko.tools.viewer.jogl.preferences.JoglViewerPreference;
import org.goko.tools.viewer.jogl.service.ICoreJoglRenderer;
import org.goko.tools.viewer.jogl.service.JoglUtils;
import org.goko.tools.viewer.jogl.shaders.EnumGokoShaderProgram;
import org.goko.tools.viewer.jogl.shaders.ShaderLoader;
import org.goko.tools.viewer.jogl.utils.render.internal.AbstractLineRenderer;
import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.PMVMatrix;
/**
* Default GCode provider renderer
* @author PsyKo
*
*/
public class RS274GCodeRenderer extends AbstractLineRenderer implements ICoreJoglRenderer, IIdBean,
IGCodeExecutionListener<ExecutionTokenState,
ExecutionToken<ExecutionTokenState>>{
private static final GkLog LOG = GkLog.getLogger(RS274GCodeRenderer.class);
/** Internal ID */
private Integer id;
/** The GCodeProvider*/
private IGCodeProvider gcodeProvider;
/** Command state layout */
private static final int STATE_LAYOUT = 2;
/** TEST : the map of vertices by ID */
private Map<Integer, VerticesGroupByLine> mapVerticesGroupByIdLine;
/** Float buffer for command state */
private IntBuffer stateBuffer;
/** The id of the state buffer object*/
private Integer stateBufferObject;
/** The 4 axis controller adapter that provides angle of the current stock*/
private IFourAxisControllerAdapter fourAxisControllerAdapter;
/** The GCode context supplier */
private IGCodeContextProvider<GCodeContext> gcodeContextProvider;
/** The map of stored states (in case line get executed before renderer is initialized) */
private Map<Integer, ExecutionTokenState> storedStates;
/**
* Constructor
* @param gcodeProvider the GCodeProvider to render
*/
public RS274GCodeRenderer(IGCodeProvider gcodeProvider, IGCodeContextProvider<GCodeContext> gcodeContextProvider, IFourAxisControllerAdapter fourAxisControllerAdapter) {
super(GL.GL_LINE_STRIP, COLORS | VERTICES);
this.gcodeProvider = gcodeProvider;
this.gcodeContextProvider = gcodeContextProvider;
this.fourAxisControllerAdapter = fourAxisControllerAdapter;
setLineWidth(1f);
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.service.AbstractCoreJoglRenderer#render(javax.media.opengl.GL3, com.jogamp.opengl.util.PMVMatrix)
*/
@Override
public void render(GL3 gl, PMVMatrix modelViewMatrix) throws GkException {
if(fourAxisControllerAdapter == null){
super.render(gl, modelViewMatrix);
}else{
// We have to render using the 4th axis
float angle = 0;
Double realAngle = fourAxisControllerAdapter.getA().doubleValue(AngleUnit.DEGREE_ANGLE);
if(realAngle != null){
angle = realAngle.floatValue();
}
Vector3f rotationAxis = JoglViewerPreference.getInstance().getRotaryAxisDirectionVector();
modelViewMatrix.glRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
super.render(gl, modelViewMatrix);
modelViewMatrix.glRotatef(angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
}
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#buildGeometry()
*/
@Override
protected void buildGeometry() throws GkException {
ArrayList<Point3d> lstVertices = new ArrayList<Point3d>();
ArrayList<Color4f> lstColors = new ArrayList<Color4f>();
mapVerticesGroupByIdLine = new HashMap<Integer, VerticesGroupByLine>();
GCodeContext context = new GCodeContext(gcodeContextProvider.getGCodeContext());
InstructionProvider instructionSet = Activator.getRS274NGCService().getInstructions(context, gcodeProvider);
IInstructionSetIterator<GCodeContext, AbstractInstruction> iterator = Activator.getRS274NGCService().getIterator(instructionSet, context);
IInstructionColorizer<GCodeContext, AbstractInstruction> colorizer = new MotionModeColorizer();
//IInstructionColorizer<GCodeContext, AbstractInstruction> colorizer = new SelectedPlaneColorizer();
//IInstructionColorizer<GCodeContext, AbstractInstruction> colorizer = new ArcAngleColorizer();
while(iterator.hasNext()){
GCodeContext preContext = new GCodeContext(iterator.getContext());
AbstractInstruction instruction = iterator.next();
// TEST : Make sure we have a complete start position for rendering.
if(preContext.getX() != null && preContext.getY() != null && preContext.getZ() != null){
List<Point3d> vertices = InstructionGeometryFactory.build(preContext, instruction);
addVerticesGroup(instruction.getIdGCodeLine(), lstVertices.size(), vertices);
lstVertices.addAll(vertices);
// Let's generate the colors and update the bounds as well
Color4f color = colorizer.getColor(preContext, instruction);
for ( int i = 0; i < vertices.size(); i++) {
lstColors.add(color);
}
}
}
setVerticesCount(CollectionUtils.size(lstVertices));
stateBuffer = IntBuffer.allocate(getVerticesCount());
stateBuffer.rewind();
setColorsBuffer(JoglUtils.buildFloatBuffer4f(lstColors));
setVerticesBuffer(JoglUtils.buildFloatBuffer3d(lstVertices));
}
@Override
protected void initializeBufferObjects(GL3 gl) throws GkException {
super.initializeBufferObjects(gl);
}
/**
* Add the given vertices to the group of vertices for this command
* @param idGCodeLine the id of the generating GCodeLine
* @param startIndex the start index
* @param vertices the vertices array
*/
private void addVerticesGroup(Integer idGCodeLine, int startIndex, List<Point3d> vertices) {
if(!mapVerticesGroupByIdLine.containsKey(idGCodeLine)){
mapVerticesGroupByIdLine.put(idGCodeLine, new VerticesGroupByLine(startIndex));
}
VerticesGroupByLine group = mapVerticesGroupByIdLine.get(idGCodeLine);
group.setLength( group.getLength() + vertices.size());
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#loadShaderProgram(javax.media.opengl.GL3)
*/
@Override
protected int loadShaderProgram(GL3 gl) throws GkException {
return ShaderLoader.loadShader(gl, EnumGokoShaderProgram.GCODE_SHADER);
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#performUpdateBufferObjects(javax.media.opengl.GL3)
*/
@Override
protected void performUpdateBufferObjects(GL3 gl) throws GkException {
super.performUpdateBufferObjects(gl);
stateBuffer.rewind();
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, stateBufferObject);
gl.glBufferData(GL.GL_ARRAY_BUFFER, getVerticesCount()*Buffers.SIZEOF_FLOAT, stateBuffer, GL.GL_DYNAMIC_DRAW);
setUpdateBuffer(false);
}
/**
* (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#updateGeometry()
*/
public void updateGeometry() throws GkException {
super.updateGeometry();
};
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#initializeAdditionalBufferObjects(javax.media.opengl.GL3)
*/
@Override
protected void initializeAdditionalBufferObjects(GL3 gl) throws GkException {
// Initialize the status buffer object
if(this.stateBufferObject == null){
int[] vbo = new int[1];
gl.glGenBuffers(1, vbo, 0);
this.stateBufferObject = vbo[0];
}
// Make sure we take everything
stateBuffer.rewind();
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, stateBufferObject);
gl.glBufferData(GL.GL_ARRAY_BUFFER, getVerticesCount()*Buffers.SIZEOF_FLOAT, stateBuffer, GL.GL_DYNAMIC_DRAW);
gl.glEnableVertexAttribArray(STATE_LAYOUT);
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#enableAdditionalVertexAttribArray(javax.media.opengl.GL3)
*/
@Override
protected void enableAdditionalVertexAttribArray(GL3 gl) throws GkException {
gl.glEnableVertexAttribArray(STATE_LAYOUT);
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, stateBufferObject);
gl.glVertexAttribPointer(STATE_LAYOUT, 1, GL3.GL_FLOAT, false, 0, 0);
}
/** (inheritDoc)
* @see org.goko.tools.viewer.jogl.utils.render.internal.AbstractVboJoglRenderer#disableAdditionalVertexAttribArray(javax.media.opengl.GL3)
*/
@Override
protected void disableAdditionalVertexAttribArray(GL3 gl) throws GkException {
gl.glDisableVertexAttribArray(STATE_LAYOUT);
}
/**
* @return the id
*/
public Integer getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(Integer id) {
this.id = id;
}
void reinitializeStateBuffer(){
if(stateBuffer != null){
int capacity = stateBuffer.capacity();
for (int i = 0; i < capacity; i++){
stateBuffer.put(i, ExecutionTokenState.NONE_STATE);
}
update();
}
}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionStart(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void onExecutionStart(ExecutionToken<ExecutionTokenState> token) throws GkException {
if(ObjectUtils.equals(token.getGCodeProvider(), gcodeProvider)){
reinitializeStateBuffer();
}
}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionComplete()
*/
@Override
public void onQueueExecutionComplete() throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionStart()
*/
@Override
public void onQueueExecutionStart() throws GkException {
reinitializeStateBuffer();
}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionCanceled(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void onExecutionCanceled(ExecutionToken<ExecutionTokenState> token) throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionCanceled()
*/
@Override
public void onQueueExecutionCanceled() throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionPause(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void onExecutionPause(ExecutionToken<ExecutionTokenState> token) throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionResume(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void onExecutionResume(ExecutionToken<ExecutionTokenState> token) throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionComplete(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void onExecutionComplete(ExecutionToken<ExecutionTokenState> token) throws GkException {}
/** (inheritDoc)
* @see org.goko.core.gcode.service.IGCodeLineExecutionListener#onLineStateChanged(org.goko.core.gcode.execution.IExecutionToken, java.lang.Integer)
*/
@Override
public void onLineStateChanged(ExecutionToken<ExecutionTokenState> token, Integer idLine) throws GkException {
if(ObjectUtils.equals(token.getGCodeProvider(), gcodeProvider)){
synchronized(this){
if(mapVerticesGroupByIdLine != null && stateBuffer != null){
if(mapVerticesGroupByIdLine.containsKey(idLine)){
// Process stored states
if(storedStates != null){
for (Integer storedLineId : storedStates.keySet()) {
updateStateBuffer(storedLineId, storedStates.get(storedLineId));
}
storedStates.clear();
storedStates = null;
}
// Process last received state
ExecutionTokenState state = token.getLineState(idLine);
updateStateBuffer(idLine, state);
}
}else{
// Renderer not initialized yet, we have to store received line
if(storedStates == null){
storedStates = new HashMap<>();
}
storedStates.put(idLine, token.getLineState(idLine));
LOG.info("Storing state line change for line ["+idLine+"] before init");
}
}
}
}
private void updateStateBuffer(Integer idLine, ExecutionTokenState state){
// Process last received state
VerticesGroupByLine group = mapVerticesGroupByIdLine.get(idLine);
if(stateBuffer != null){
// Make sure the line created renderable items (an empty line, not creating instruction, will be skipped)
if(group != null){
for (int i = group.getStartIndex(); i < group.getStartIndex() + group.getLength(); i++) {
stateBuffer.put(i, state.getState());
}
}
update();
}
}
/**
* @return the gcodeContextProvider
*/
public IGCodeContextProvider<GCodeContext> getGCodeContextProvider() {
return gcodeContextProvider;
}
/**
* @param gcodeContextProvider the gcodeContextProvider to set
*/
public void setGCodeContextProvider(IGCodeContextProvider<GCodeContext> gcodeContextProvider) {
this.gcodeContextProvider = gcodeContextProvider;
}
/**
* @return the gcodeProvider
*/
public IGCodeProvider getGCodeProvider() {
return gcodeProvider;
}
}
/**
* Inner class describing a position and a number of vertices referring to the samed GCodeLine
*
* @author Psyko
*/
class VerticesGroupByLine{
private int startIndex;
private int length;
public VerticesGroupByLine(int startIndex) {
super();
this.startIndex = startIndex;
}
/**
* @return the startIndex
*/
public int getStartIndex() {
return startIndex;
}
/**
* @param startIndex the startIndex to set
*/
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
/**
* @return the length
*/
public int getLength() {
return length;
}
/**
* @param length the length to set
*/
public void setLength(int length) {
this.length = length;
}
}